[Alerting] Refactor alerts authorization client (#99078)

* WIP - creating alerting authorization client factory and exposing authorization client on plugin start contract

* Updating alerting feature privilege builder to handle different alerting types

* Passing in alerting authorization type to AlertingActions class string builder

* Passing in authorization type in each function call

* Passing in exempt consumer ids. Adding authorization type to audit logger

* Changing alertType to ruleType

* Changing alertType to ruleType

* Updating unit tests

* Updating unit tests

* Passing field names into authorization query builder. Adding kql/es dsl option

* Converting to es query if requested

* Fixing functional tests

* Removing ability to specify feature privilege name in constructor

* Fixing some types and tests

* Consolidating alerting authorization kuery filter options

* Cleanup and tests

* Cleanup and tests

* Throwing error when AlertingAuthorizationClientFactory is not defined

* Renaming authorizationType to entity

* Renaming AlertsAuthorization to AlertingAuthorization

* Fixing unit tests

* Updating privilege string terminology

* Updating privilege string terminology

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
ymao1 2021-05-18 18:32:43 -04:00 committed by GitHub
parent a105c7a8b1
commit 0f0cee2510
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 2461 additions and 1953 deletions

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory';
const creatAlertingAuthorizationClientFactoryMock = () => {
const mocked: jest.Mocked<PublicMethodsOf<AlertingAuthorizationClientFactory>> = {
create: jest.fn(),
initialize: jest.fn(),
};
return mocked;
};
export const alertingAuthorizationClientFactoryMock = {
createFactory: creatAlertingAuthorizationClientFactoryMock,
};

View file

@ -0,0 +1,130 @@
/*
* 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 { Request } from '@hapi/hapi';
import { alertTypeRegistryMock } from './alert_type_registry.mock';
import { KibanaRequest } from '../../../../src/core/server';
import { savedObjectsClientMock } from '../../../../src/core/server/mocks';
import { securityMock } from '../../security/server/mocks';
import { ALERTS_FEATURE_ID } from '../common';
import {
AlertingAuthorizationClientFactory,
AlertingAuthorizationClientFactoryOpts,
} from './alerting_authorization_client_factory';
import { featuresPluginMock } from '../../features/server/mocks';
jest.mock('./authorization/alerting_authorization');
jest.mock('./authorization/audit_logger');
const savedObjectsClient = savedObjectsClientMock.create();
const features = featuresPluginMock.createStart();
const securityPluginSetup = securityMock.createSetup();
const securityPluginStart = securityMock.createStart();
const alertingAuthorizationClientFactoryParams: jest.Mocked<AlertingAuthorizationClientFactoryOpts> = {
alertTypeRegistry: alertTypeRegistryMock.create(),
getSpace: jest.fn(),
features,
};
const fakeRequest = ({
app: {},
headers: {},
getBasePath: () => '',
path: '/',
route: { settings: {} },
url: {
href: '/',
},
raw: {
req: {
url: '/',
},
},
getSavedObjectsClient: () => savedObjectsClient,
} as unknown) as Request;
beforeEach(() => {
jest.resetAllMocks();
});
test('creates an alerting authorization client with proper constructor arguments when security is enabled', async () => {
const factory = new AlertingAuthorizationClientFactory();
factory.initialize({
securityPluginSetup,
securityPluginStart,
...alertingAuthorizationClientFactoryParams,
});
const request = KibanaRequest.from(fakeRequest);
const { AlertingAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger');
factory.create(request);
const { AlertingAuthorization } = jest.requireMock('./authorization/alerting_authorization');
expect(AlertingAuthorization).toHaveBeenCalledWith({
request,
authorization: securityPluginStart.authz,
alertTypeRegistry: alertingAuthorizationClientFactoryParams.alertTypeRegistry,
features: alertingAuthorizationClientFactoryParams.features,
auditLogger: expect.any(AlertingAuthorizationAuditLogger),
getSpace: expect.any(Function),
exemptConsumerIds: [],
});
expect(AlertingAuthorizationAuditLogger).toHaveBeenCalled();
expect(securityPluginSetup.audit.getLogger).toHaveBeenCalledWith(ALERTS_FEATURE_ID);
});
test('creates an alerting authorization client with proper constructor arguments when exemptConsumerIds are specified', async () => {
const factory = new AlertingAuthorizationClientFactory();
factory.initialize({
securityPluginSetup,
securityPluginStart,
...alertingAuthorizationClientFactoryParams,
});
const request = KibanaRequest.from(fakeRequest);
const { AlertingAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger');
factory.create(request, ['exemptConsumerA', 'exemptConsumerB']);
const { AlertingAuthorization } = jest.requireMock('./authorization/alerting_authorization');
expect(AlertingAuthorization).toHaveBeenCalledWith({
request,
authorization: securityPluginStart.authz,
alertTypeRegistry: alertingAuthorizationClientFactoryParams.alertTypeRegistry,
features: alertingAuthorizationClientFactoryParams.features,
auditLogger: expect.any(AlertingAuthorizationAuditLogger),
getSpace: expect.any(Function),
exemptConsumerIds: ['exemptConsumerA', 'exemptConsumerB'],
});
expect(AlertingAuthorizationAuditLogger).toHaveBeenCalled();
expect(securityPluginSetup.audit.getLogger).toHaveBeenCalledWith(ALERTS_FEATURE_ID);
});
test('creates an alerting authorization client with proper constructor arguments', async () => {
const factory = new AlertingAuthorizationClientFactory();
factory.initialize(alertingAuthorizationClientFactoryParams);
const request = KibanaRequest.from(fakeRequest);
const { AlertingAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger');
factory.create(request);
const { AlertingAuthorization } = jest.requireMock('./authorization/alerting_authorization');
expect(AlertingAuthorization).toHaveBeenCalledWith({
request,
alertTypeRegistry: alertingAuthorizationClientFactoryParams.alertTypeRegistry,
features: alertingAuthorizationClientFactoryParams.features,
auditLogger: expect.any(AlertingAuthorizationAuditLogger),
getSpace: expect.any(Function),
exemptConsumerIds: [],
});
expect(AlertingAuthorizationAuditLogger).toHaveBeenCalled();
expect(securityPluginSetup.audit.getLogger).not.toHaveBeenCalled();
});

View file

@ -0,0 +1,59 @@
/*
* 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 { KibanaRequest } from 'src/core/server';
import { ALERTS_FEATURE_ID } from '../common';
import { AlertTypeRegistry } from './types';
import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server';
import { PluginStartContract as FeaturesPluginStart } from '../../features/server';
import { AlertingAuthorization } from './authorization/alerting_authorization';
import { AlertingAuthorizationAuditLogger } from './authorization/audit_logger';
import { Space } from '../../spaces/server';
export interface AlertingAuthorizationClientFactoryOpts {
alertTypeRegistry: AlertTypeRegistry;
securityPluginSetup?: SecurityPluginSetup;
securityPluginStart?: SecurityPluginStart;
getSpace: (request: KibanaRequest) => Promise<Space | undefined>;
features: FeaturesPluginStart;
}
export class AlertingAuthorizationClientFactory {
private isInitialized = false;
private alertTypeRegistry!: AlertTypeRegistry;
private securityPluginStart?: SecurityPluginStart;
private securityPluginSetup?: SecurityPluginSetup;
private features!: FeaturesPluginStart;
private getSpace!: (request: KibanaRequest) => Promise<Space | undefined>;
public initialize(options: AlertingAuthorizationClientFactoryOpts) {
if (this.isInitialized) {
throw new Error('AlertingAuthorizationClientFactory already initialized');
}
this.isInitialized = true;
this.getSpace = options.getSpace;
this.alertTypeRegistry = options.alertTypeRegistry;
this.securityPluginSetup = options.securityPluginSetup;
this.securityPluginStart = options.securityPluginStart;
this.features = options.features;
}
public create(request: KibanaRequest, exemptConsumerIds: string[] = []): AlertingAuthorization {
const { securityPluginSetup, securityPluginStart, features } = this;
return new AlertingAuthorization({
authorization: securityPluginStart?.authz,
request,
getSpace: this.getSpace,
alertTypeRegistry: this.alertTypeRegistry,
features: features!,
auditLogger: new AlertingAuthorizationAuditLogger(
securityPluginSetup?.audit.getLogger(ALERTS_FEATURE_ID)
),
exemptConsumerIds,
});
}
}

View file

@ -46,7 +46,14 @@ import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/se
import { TaskManagerStartContract } from '../../../task_manager/server';
import { taskInstanceToAlertTaskInstance } from '../task_runner/alert_task_instance';
import { RegistryAlertType, UntypedNormalizedAlertType } from '../alert_type_registry';
import { AlertsAuthorization, WriteOperations, ReadOperations } from '../authorization';
import {
AlertingAuthorization,
WriteOperations,
ReadOperations,
AlertingAuthorizationEntity,
AlertingAuthorizationFilterType,
AlertingAuthorizationFilterOpts,
} from '../authorization';
import { IEventLogClient } from '../../../../plugins/event_log/server';
import { parseIsoOrRelativeDate } from '../lib/iso_or_relative_date';
import { alertInstanceSummaryFromEventLog } from '../lib/alert_instance_summary_from_event_log';
@ -57,7 +64,7 @@ import { retryIfConflicts } from '../lib/retry_if_conflicts';
import { partiallyUpdateAlert } from '../saved_objects';
import { markApiKeyForInvalidation } from '../invalidate_pending_api_keys/mark_api_key_for_invalidation';
import { alertAuditEvent, AlertAuditAction } from './audit_events';
import { nodeBuilder } from '../../../../../src/plugins/data/common';
import { KueryNode, nodeBuilder } from '../../../../../src/plugins/data/common';
import { mapSortField } from './lib';
import { getAlertExecutionStatusPending } from '../lib/alert_execution_status';
@ -76,7 +83,7 @@ export interface ConstructorOptions {
logger: Logger;
taskManager: TaskManagerStartContract;
unsecuredSavedObjectsClient: SavedObjectsClientContract;
authorization: AlertsAuthorization;
authorization: AlertingAuthorization;
actionsAuthorization: ActionsAuthorization;
alertTypeRegistry: AlertTypeRegistry;
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
@ -176,6 +183,10 @@ export interface GetAlertInstanceSummaryParams {
dateStart?: string;
}
const alertingAuthorizationFilterOpts: AlertingAuthorizationFilterOpts = {
type: AlertingAuthorizationFilterType.KQL,
fieldNames: { ruleTypeId: 'alert.attributes.alertTypeId', consumer: 'alert.attributes.consumer' },
};
export class AlertsClient {
private readonly logger: Logger;
private readonly getUserName: () => Promise<string | null>;
@ -183,7 +194,7 @@ export class AlertsClient {
private readonly namespace?: string;
private readonly taskManager: TaskManagerStartContract;
private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract;
private readonly authorization: AlertsAuthorization;
private readonly authorization: AlertingAuthorization;
private readonly alertTypeRegistry: AlertTypeRegistry;
private readonly createAPIKey: (name: string) => Promise<CreateAPIKeyResult>;
private readonly getActionsClient: () => Promise<ActionsClient>;
@ -234,11 +245,12 @@ export class AlertsClient {
const id = options?.id || SavedObjectsUtils.generateId();
try {
await this.authorization.ensureAuthorized(
data.alertTypeId,
data.consumer,
WriteOperations.Create
);
await this.authorization.ensureAuthorized({
ruleTypeId: data.alertTypeId,
consumer: data.consumer,
operation: WriteOperations.Create,
entity: AlertingAuthorizationEntity.Rule,
});
} catch (error) {
this.auditLogger?.log(
alertAuditEvent({
@ -355,11 +367,12 @@ export class AlertsClient {
}): Promise<SanitizedAlert<Params>> {
const result = await this.unsecuredSavedObjectsClient.get<RawAlert>('alert', id);
try {
await this.authorization.ensureAuthorized(
result.attributes.alertTypeId,
result.attributes.consumer,
ReadOperations.Get
);
await this.authorization.ensureAuthorized({
ruleTypeId: result.attributes.alertTypeId,
consumer: result.attributes.consumer,
operation: ReadOperations.Get,
entity: AlertingAuthorizationEntity.Rule,
});
} catch (error) {
this.auditLogger?.log(
alertAuditEvent({
@ -381,11 +394,12 @@ export class AlertsClient {
public async getAlertState({ id }: { id: string }): Promise<AlertTaskState | void> {
const alert = await this.get({ id });
await this.authorization.ensureAuthorized(
alert.alertTypeId,
alert.consumer,
ReadOperations.GetAlertState
);
await this.authorization.ensureAuthorized({
ruleTypeId: alert.alertTypeId,
consumer: alert.consumer,
operation: ReadOperations.GetRuleState,
entity: AlertingAuthorizationEntity.Rule,
});
if (alert.scheduledTaskId) {
const { state } = taskInstanceToAlertTaskInstance(
await this.taskManager.get(alert.scheduledTaskId),
@ -401,11 +415,12 @@ export class AlertsClient {
}: GetAlertInstanceSummaryParams): Promise<AlertInstanceSummary> {
this.logger.debug(`getAlertInstanceSummary(): getting alert ${id}`);
const alert = await this.get({ id });
await this.authorization.ensureAuthorized(
alert.alertTypeId,
alert.consumer,
ReadOperations.GetAlertInstanceSummary
);
await this.authorization.ensureAuthorized({
ruleTypeId: alert.alertTypeId,
consumer: alert.consumer,
operation: ReadOperations.GetAlertSummary,
entity: AlertingAuthorizationEntity.Rule,
});
// default duration of instance summary is 60 * alert interval
const dateNow = new Date();
@ -446,7 +461,10 @@ export class AlertsClient {
}: { options?: FindOptions } = {}): Promise<FindResult<Params>> {
let authorizationTuple;
try {
authorizationTuple = await this.authorization.getFindAuthorizationFilter();
authorizationTuple = await this.authorization.getFindAuthorizationFilter(
AlertingAuthorizationEntity.Rule,
alertingAuthorizationFilterOpts
);
} catch (error) {
this.auditLogger?.log(
alertAuditEvent({
@ -458,7 +476,7 @@ export class AlertsClient {
}
const {
filter: authorizationFilter,
ensureAlertTypeIsAuthorized,
ensureRuleTypeIsAuthorized,
logSuccessfulAuthorization,
} = authorizationTuple;
@ -472,7 +490,10 @@ export class AlertsClient {
sortField: mapSortField(options.sortField),
filter:
(authorizationFilter && options.filter
? nodeBuilder.and([esKuery.fromKueryExpression(options.filter), authorizationFilter])
? nodeBuilder.and([
esKuery.fromKueryExpression(options.filter),
authorizationFilter as KueryNode,
])
: authorizationFilter) ?? options.filter,
fields: fields ? this.includeFieldsRequiredForAuthentication(fields) : fields,
type: 'alert',
@ -480,7 +501,11 @@ export class AlertsClient {
const authorizedData = data.map(({ id, attributes, references }) => {
try {
ensureAlertTypeIsAuthorized(attributes.alertTypeId, attributes.consumer);
ensureRuleTypeIsAuthorized(
attributes.alertTypeId,
attributes.consumer,
AlertingAuthorizationEntity.Rule
);
} catch (error) {
this.auditLogger?.log(
alertAuditEvent({
@ -526,7 +551,10 @@ export class AlertsClient {
const {
filter: authorizationFilter,
logSuccessfulAuthorization,
} = await this.authorization.getFindAuthorizationFilter();
} = await this.authorization.getFindAuthorizationFilter(
AlertingAuthorizationEntity.Rule,
alertingAuthorizationFilterOpts
);
const filter = options.filter
? `${options.filter} and alert.attributes.executionStatus.status:(${status})`
: `alert.attributes.executionStatus.status:(${status})`;
@ -534,7 +562,10 @@ export class AlertsClient {
...options,
filter:
(authorizationFilter && filter
? nodeBuilder.and([esKuery.fromKueryExpression(filter), authorizationFilter])
? nodeBuilder.and([
esKuery.fromKueryExpression(filter),
authorizationFilter as KueryNode,
])
: authorizationFilter) ?? filter,
page: 1,
perPage: 0,
@ -581,11 +612,12 @@ export class AlertsClient {
}
try {
await this.authorization.ensureAuthorized(
attributes.alertTypeId,
attributes.consumer,
WriteOperations.Delete
);
await this.authorization.ensureAuthorized({
ruleTypeId: attributes.alertTypeId,
consumer: attributes.consumer,
operation: WriteOperations.Delete,
entity: AlertingAuthorizationEntity.Rule,
});
} catch (error) {
this.auditLogger?.log(
alertAuditEvent({
@ -654,11 +686,12 @@ export class AlertsClient {
}
try {
await this.authorization.ensureAuthorized(
alertSavedObject.attributes.alertTypeId,
alertSavedObject.attributes.consumer,
WriteOperations.Update
);
await this.authorization.ensureAuthorized({
ruleTypeId: alertSavedObject.attributes.alertTypeId,
consumer: alertSavedObject.attributes.consumer,
operation: WriteOperations.Update,
entity: AlertingAuthorizationEntity.Rule,
});
} catch (error) {
this.auditLogger?.log(
alertAuditEvent({
@ -826,11 +859,12 @@ export class AlertsClient {
}
try {
await this.authorization.ensureAuthorized(
attributes.alertTypeId,
attributes.consumer,
WriteOperations.UpdateApiKey
);
await this.authorization.ensureAuthorized({
ruleTypeId: attributes.alertTypeId,
consumer: attributes.consumer,
operation: WriteOperations.UpdateApiKey,
entity: AlertingAuthorizationEntity.Rule,
});
if (attributes.actions.length) {
await this.actionsAuthorization.ensureAuthorized('execute');
}
@ -930,11 +964,12 @@ export class AlertsClient {
}
try {
await this.authorization.ensureAuthorized(
attributes.alertTypeId,
attributes.consumer,
WriteOperations.Enable
);
await this.authorization.ensureAuthorized({
ruleTypeId: attributes.alertTypeId,
consumer: attributes.consumer,
operation: WriteOperations.Enable,
entity: AlertingAuthorizationEntity.Rule,
});
if (attributes.actions.length) {
await this.actionsAuthorization.ensureAuthorized('execute');
@ -1047,11 +1082,12 @@ export class AlertsClient {
}
try {
await this.authorization.ensureAuthorized(
attributes.alertTypeId,
attributes.consumer,
WriteOperations.Disable
);
await this.authorization.ensureAuthorized({
ruleTypeId: attributes.alertTypeId,
consumer: attributes.consumer,
operation: WriteOperations.Disable,
entity: AlertingAuthorizationEntity.Rule,
});
} catch (error) {
this.auditLogger?.log(
alertAuditEvent({
@ -1119,11 +1155,12 @@ export class AlertsClient {
);
try {
await this.authorization.ensureAuthorized(
attributes.alertTypeId,
attributes.consumer,
WriteOperations.MuteAll
);
await this.authorization.ensureAuthorized({
ruleTypeId: attributes.alertTypeId,
consumer: attributes.consumer,
operation: WriteOperations.MuteAll,
entity: AlertingAuthorizationEntity.Rule,
});
if (attributes.actions.length) {
await this.actionsAuthorization.ensureAuthorized('execute');
@ -1180,11 +1217,12 @@ export class AlertsClient {
);
try {
await this.authorization.ensureAuthorized(
attributes.alertTypeId,
attributes.consumer,
WriteOperations.UnmuteAll
);
await this.authorization.ensureAuthorized({
ruleTypeId: attributes.alertTypeId,
consumer: attributes.consumer,
operation: WriteOperations.UnmuteAll,
entity: AlertingAuthorizationEntity.Rule,
});
if (attributes.actions.length) {
await this.actionsAuthorization.ensureAuthorized('execute');
@ -1241,11 +1279,12 @@ export class AlertsClient {
);
try {
await this.authorization.ensureAuthorized(
attributes.alertTypeId,
attributes.consumer,
WriteOperations.MuteInstance
);
await this.authorization.ensureAuthorized({
ruleTypeId: attributes.alertTypeId,
consumer: attributes.consumer,
operation: WriteOperations.MuteAlert,
entity: AlertingAuthorizationEntity.Rule,
});
if (attributes.actions.length) {
await this.actionsAuthorization.ensureAuthorized('execute');
@ -1308,11 +1347,12 @@ export class AlertsClient {
);
try {
await this.authorization.ensureAuthorized(
attributes.alertTypeId,
attributes.consumer,
WriteOperations.UnmuteInstance
);
await this.authorization.ensureAuthorized({
ruleTypeId: attributes.alertTypeId,
consumer: attributes.consumer,
operation: WriteOperations.UnmuteAlert,
entity: AlertingAuthorizationEntity.Rule,
});
if (attributes.actions.length) {
await this.actionsAuthorization.ensureAuthorized('execute');
}
@ -1353,10 +1393,11 @@ export class AlertsClient {
}
public async listAlertTypes() {
return await this.authorization.filterByAlertTypeAuthorization(this.alertTypeRegistry.list(), [
ReadOperations.Get,
WriteOperations.Create,
]);
return await this.authorization.filterByRuleTypeAuthorization(
this.alertTypeRegistry.list(),
[ReadOperations.Get, WriteOperations.Create],
AlertingAuthorizationEntity.Rule
);
}
private async scheduleAlert(id: string, alertTypeId: string, schedule: IntervalSchedule) {

View file

@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { getBeforeSetup, setGlobalDate } from './lib';
import { AlertExecutionStatusValues } from '../../types';
@ -24,7 +24,7 @@ const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const kibanaVersion = 'v7.10.0';
@ -32,7 +32,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -67,7 +67,7 @@ describe('aggregate()', () => {
]);
beforeEach(() => {
authorization.getFindAuthorizationFilter.mockResolvedValue({
ensureAlertTypeIsAuthorized() {},
ensureRuleTypeIsAuthorized() {},
logSuccessfulAuthorization() {},
});
unsecuredSavedObjectsClient.find
@ -102,7 +102,7 @@ describe('aggregate()', () => {
saved_objects: [],
});
alertTypeRegistry.list.mockReturnValue(listedTypes);
authorization.filterByAlertTypeAuthorization.mockResolvedValue(
authorization.filterByRuleTypeAuthorization.mockResolvedValue(
new Set([
{
id: 'myType',

View file

@ -10,10 +10,10 @@ import { AlertsClient, ConstructorOptions, CreateOptions } from '../alerts_clien
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization, ActionsClient } from '../../../../actions/server';
import { TaskStatus } from '../../../../task_manager/server';
import { auditServiceMock } from '../../../../security/server/audit/index.mock';
@ -31,7 +31,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -40,7 +40,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -194,7 +194,12 @@ describe('create()', () => {
await tryToExecuteOperation({ data });
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'create');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'create',
ruleTypeId: 'myType',
});
});
test('throws when user is not authorised to create this type of alert', async () => {
@ -211,7 +216,12 @@ describe('create()', () => {
`[Error: Unauthorized to create a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'create');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'create',
ruleTypeId: 'myType',
});
});
});
@ -338,7 +348,12 @@ describe('create()', () => {
],
});
const result = await alertsClient.create({ data });
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('123', 'bar', 'create');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'bar',
operation: 'create',
ruleTypeId: '123',
});
expect(result).toMatchInlineSnapshot(`
Object {
"actions": Array [

View file

@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { httpServerMock } from '../../../../../../src/core/server/mocks';
import { auditServiceMock } from '../../../../security/server/audit/index.mock';
@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -231,7 +231,12 @@ describe('delete()', () => {
test('ensures user is authorised to delete this type of alert under the consumer', async () => {
await alertsClient.delete({ id: '1' });
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'delete');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'delete',
ruleTypeId: 'myType',
});
});
test('throws when user is not authorised to delete this type of alert', async () => {
@ -243,7 +248,12 @@ describe('delete()', () => {
`[Error: Unauthorized to delete a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'delete');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'delete',
ruleTypeId: 'myType',
});
});
});

View file

@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { InvalidatePendingApiKey } from '../../types';
import { httpServerMock } from '../../../../../../src/core/server/mocks';
@ -23,7 +23,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -32,7 +32,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -99,7 +99,12 @@ describe('disable()', () => {
test('ensures user is authorised to disable this type of alert under the consumer', async () => {
await alertsClient.disable({ id: '1' });
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'disable');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'disable',
ruleTypeId: 'myType',
});
});
test('throws when user is not authorised to disable this type of alert', async () => {
@ -111,7 +116,12 @@ describe('disable()', () => {
`[Error: Unauthorized to disable a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'disable');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'disable',
ruleTypeId: 'myType',
});
});
});

View file

@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { TaskStatus } from '../../../../task_manager/server';
import { httpServerMock } from '../../../../../../src/core/server/mocks';
@ -24,7 +24,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -33,7 +33,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -137,7 +137,12 @@ describe('enable()', () => {
test('ensures user is authorised to enable this type of alert under the consumer', async () => {
await alertsClient.enable({ id: '1' });
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'enable');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'enable',
ruleTypeId: 'myType',
});
expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute');
});
@ -150,7 +155,12 @@ describe('enable()', () => {
`[Error: Unauthorized to enable a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'enable');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'enable',
ruleTypeId: 'myType',
});
});
});

View file

@ -9,12 +9,12 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { nodeTypes } from '../../../../../../src/plugins/data/common';
import { esKuery } from '../../../../../../src/plugins/data/server';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { httpServerMock } from '../../../../../../src/core/server/mocks';
import { auditServiceMock } from '../../../../security/server/audit/index.mock';
@ -26,7 +26,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -35,7 +35,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -75,7 +75,7 @@ describe('find()', () => {
]);
beforeEach(() => {
authorization.getFindAuthorizationFilter.mockResolvedValue({
ensureAlertTypeIsAuthorized() {},
ensureRuleTypeIsAuthorized() {},
logSuccessfulAuthorization() {},
});
unsecuredSavedObjectsClient.find.mockResolvedValueOnce({
@ -117,7 +117,7 @@ describe('find()', () => {
],
});
alertTypeRegistry.list.mockReturnValue(listedTypes);
authorization.filterByAlertTypeAuthorization.mockResolvedValue(
authorization.filterByRuleTypeAuthorization.mockResolvedValue(
new Set([
{
id: 'myType',
@ -196,7 +196,7 @@ describe('find()', () => {
);
authorization.getFindAuthorizationFilter.mockResolvedValue({
filter,
ensureAlertTypeIsAuthorized() {},
ensureRuleTypeIsAuthorized() {},
logSuccessfulAuthorization() {},
});
@ -219,10 +219,10 @@ describe('find()', () => {
});
test('ensures authorization even when the fields required to authorize are omitted from the find', async () => {
const ensureAlertTypeIsAuthorized = jest.fn();
const ensureRuleTypeIsAuthorized = jest.fn();
const logSuccessfulAuthorization = jest.fn();
authorization.getFindAuthorizationFilter.mockResolvedValue({
ensureAlertTypeIsAuthorized,
ensureRuleTypeIsAuthorized,
logSuccessfulAuthorization,
});
@ -271,7 +271,7 @@ describe('find()', () => {
fields: ['tags', 'alertTypeId', 'consumer'],
type: 'alert',
});
expect(ensureAlertTypeIsAuthorized).toHaveBeenCalledWith('myType', 'myApp');
expect(ensureRuleTypeIsAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'rule');
expect(logSuccessfulAuthorization).toHaveBeenCalled();
});
});
@ -313,7 +313,7 @@ describe('find()', () => {
test('logs audit event when not authorised to search alert type', async () => {
const alertsClient = new AlertsClient({ ...alertsClientParams, auditLogger });
authorization.getFindAuthorizationFilter.mockResolvedValue({
ensureAlertTypeIsAuthorized: jest.fn(() => {
ensureRuleTypeIsAuthorized: jest.fn(() => {
throw new Error('Unauthorized');
}),
logSuccessfulAuthorization: jest.fn(),

View file

@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { httpServerMock } from '../../../../../../src/core/server/mocks';
import { auditServiceMock } from '../../../../security/server/audit/index.mock';
@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -182,7 +182,12 @@ describe('get()', () => {
const alertsClient = new AlertsClient(alertsClientParams);
await alertsClient.get({ id: '1' });
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'get');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'get',
ruleTypeId: 'myType',
});
});
test('throws when user is not authorised to get this type of alert', async () => {
@ -195,7 +200,12 @@ describe('get()', () => {
`[Error: Unauthorized to get a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'get');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'get',
ruleTypeId: 'myType',
});
});
});

View file

@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { eventLogClientMock } from '../../../../event_log/server/mocks';
import { QueryEventsBySavedObjectResult } from '../../../../event_log/server';
@ -27,7 +27,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const eventLogClient = eventLogClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const kibanaVersion = 'v7.10.0';
@ -35,7 +35,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',

View file

@ -9,11 +9,11 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { TaskStatus } from '../../../../task_manager/server';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { getBeforeSetup } from './lib';
@ -22,7 +22,7 @@ const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const kibanaVersion = 'v7.10.0';
@ -30,7 +30,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -210,31 +210,33 @@ describe('getAlertState()', () => {
const alertsClient = new AlertsClient(alertsClientParams);
await alertsClient.getAlertState({ id: '1' });
expect(authorization.ensureAuthorized).toHaveBeenCalledWith(
'myType',
'myApp',
'getAlertState'
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'getRuleState',
ruleTypeId: 'myType',
});
});
test('throws when user is not authorised to getAlertState this type of alert', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
// `get` check
authorization.ensureAuthorized.mockResolvedValueOnce();
// `getAlertState` check
// `getRuleState` check
authorization.ensureAuthorized.mockRejectedValueOnce(
new Error(`Unauthorized to getAlertState a "myType" alert for "myApp"`)
new Error(`Unauthorized to getRuleState a "myType" alert for "myApp"`)
);
await expect(alertsClient.getAlertState({ id: '1' })).rejects.toMatchInlineSnapshot(
`[Error: Unauthorized to getAlertState a "myType" alert for "myApp"]`
`[Error: Unauthorized to getRuleState a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith(
'myType',
'myApp',
'getAlertState'
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'getRuleState',
ruleTypeId: 'myType',
});
});
});
});

View file

@ -9,13 +9,13 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import {
AlertsAuthorization,
AlertingAuthorization,
RegistryAlertTypeWithAuth,
} from '../../authorization/alerts_authorization';
} from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { getBeforeSetup } from './lib';
import { RecoveredActionGroup } from '../../../common';
@ -26,7 +26,7 @@ const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const kibanaVersion = 'v7.10.0';
@ -34,7 +34,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -89,7 +89,7 @@ describe('listAlertTypes', () => {
test('should return a list of AlertTypes that exist in the registry', async () => {
alertTypeRegistry.list.mockReturnValue(setOfAlertTypes);
authorization.filterByAlertTypeAuthorization.mockResolvedValue(
authorization.filterByRuleTypeAuthorization.mockResolvedValue(
new Set<RegistryAlertTypeWithAuth>([
{ ...myAppAlertType, authorizedConsumers },
{ ...alertingAlertType, authorizedConsumers },
@ -147,7 +147,7 @@ describe('listAlertTypes', () => {
enabledInLicense: true,
},
]);
authorization.filterByAlertTypeAuthorization.mockResolvedValue(authorizedTypes);
authorization.filterByRuleTypeAuthorization.mockResolvedValue(authorizedTypes);
expect(await alertsClient.listAlertTypes()).toEqual(authorizedTypes);
});

View file

@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { httpServerMock } from '../../../../../../src/core/server/mocks';
import { auditServiceMock } from '../../../../security/server/audit/index.mock';
@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -126,7 +126,12 @@ describe('muteAll()', () => {
const alertsClient = new AlertsClient(alertsClientParams);
await alertsClient.muteAll({ id: '1' });
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'muteAll');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'muteAll',
ruleTypeId: 'myType',
});
expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute');
});
@ -140,7 +145,12 @@ describe('muteAll()', () => {
`[Error: Unauthorized to muteAll a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'muteAll');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'muteAll',
ruleTypeId: 'myType',
});
});
});

View file

@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { httpServerMock } from '../../../../../../src/core/server/mocks';
import { auditServiceMock } from '../../../../security/server/audit/index.mock';
@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -159,30 +159,32 @@ describe('muteInstance()', () => {
await alertsClient.muteInstance({ alertId: '1', alertInstanceId: '2' });
expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith(
'myType',
'myApp',
'muteInstance'
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'muteAlert',
ruleTypeId: 'myType',
});
});
test('throws when user is not authorised to muteInstance this type of alert', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
authorization.ensureAuthorized.mockRejectedValue(
new Error(`Unauthorized to muteInstance a "myType" alert for "myApp"`)
new Error(`Unauthorized to muteAlert a "myType" alert for "myApp"`)
);
await expect(
alertsClient.muteInstance({ alertId: '1', alertInstanceId: '2' })
).rejects.toMatchInlineSnapshot(
`[Error: Unauthorized to muteInstance a "myType" alert for "myApp"]`
`[Error: Unauthorized to muteAlert a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith(
'myType',
'myApp',
'muteInstance'
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'muteAlert',
ruleTypeId: 'myType',
});
});
});

View file

@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { httpServerMock } from '../../../../../../src/core/server/mocks';
import { auditServiceMock } from '../../../../security/server/audit/index.mock';
@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -126,7 +126,12 @@ describe('unmuteAll()', () => {
const alertsClient = new AlertsClient(alertsClientParams);
await alertsClient.unmuteAll({ id: '1' });
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'unmuteAll');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'unmuteAll',
ruleTypeId: 'myType',
});
expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute');
});
@ -140,7 +145,12 @@ describe('unmuteAll()', () => {
`[Error: Unauthorized to unmuteAll a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'unmuteAll');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'unmuteAll',
ruleTypeId: 'myType',
});
});
});

View file

@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { httpServerMock } from '../../../../../../src/core/server/mocks';
import { auditServiceMock } from '../../../../security/server/audit/index.mock';
@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -157,30 +157,32 @@ describe('unmuteInstance()', () => {
await alertsClient.unmuteInstance({ alertId: '1', alertInstanceId: '2' });
expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith(
'myType',
'myApp',
'unmuteInstance'
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'unmuteAlert',
ruleTypeId: 'myType',
});
});
test('throws when user is not authorised to unmuteInstance this type of alert', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
authorization.ensureAuthorized.mockRejectedValue(
new Error(`Unauthorized to unmuteInstance a "myType" alert for "myApp"`)
new Error(`Unauthorized to unmuteAlert a "myType" alert for "myApp"`)
);
await expect(
alertsClient.unmuteInstance({ alertId: '1', alertInstanceId: '2' })
).rejects.toMatchInlineSnapshot(
`[Error: Unauthorized to unmuteInstance a "myType" alert for "myApp"]`
`[Error: Unauthorized to unmuteAlert a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith(
'myType',
'myApp',
'unmuteInstance'
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'unmuteAlert',
ruleTypeId: 'myType',
});
});
});

View file

@ -11,12 +11,12 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { IntervalSchedule, InvalidatePendingApiKey } from '../../types';
import { RecoveredActionGroup } from '../../../common';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { resolvable } from '../../test_utils';
import { ActionsAuthorization, ActionsClient } from '../../../../actions/server';
import { TaskStatus } from '../../../../task_manager/server';
@ -28,7 +28,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -37,7 +37,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -1415,7 +1415,12 @@ describe('update()', () => {
},
});
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'update');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'update',
ruleTypeId: 'myType',
});
});
test('throws when user is not authorised to update this type of alert', async () => {
@ -1442,7 +1447,12 @@ describe('update()', () => {
`[Error: Unauthorized to update a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'update');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'update',
ruleTypeId: 'myType',
});
});
});

View file

@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { AlertingAuthorization } from '../../authorization/alerting_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { httpServerMock } from '../../../../../../src/core/server/mocks';
import { auditServiceMock } from '../../../../security/server/audit/index.mock';
@ -23,7 +23,7 @@ const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest());
@ -32,7 +32,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
@ -268,11 +268,12 @@ describe('updateApiKey()', () => {
await alertsClient.updateApiKey({ id: '1' });
expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute');
expect(authorization.ensureAuthorized).toHaveBeenCalledWith(
'myType',
'myApp',
'updateApiKey'
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'updateApiKey',
ruleTypeId: 'myType',
});
});
test('throws when user is not authorised to updateApiKey this type of alert', async () => {
@ -284,11 +285,12 @@ describe('updateApiKey()', () => {
`[Error: Unauthorized to updateApiKey a "myType" alert for "myApp"]`
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith(
'myType',
'myApp',
'updateApiKey'
);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith({
entity: 'rule',
consumer: 'myApp',
operation: 'updateApiKey',
ruleTypeId: 'myType',
});
});
});

View file

@ -11,10 +11,10 @@ import { AlertsClient, ConstructorOptions } from './alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../src/core/server/mocks';
import { taskManagerMock } from '../../task_manager/server/mocks';
import { alertTypeRegistryMock } from './alert_type_registry.mock';
import { alertsAuthorizationMock } from './authorization/alerts_authorization.mock';
import { alertingAuthorizationMock } from './authorization/alerting_authorization.mock';
import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks';
import { actionsClientMock, actionsAuthorizationMock } from '../../actions/server/mocks';
import { AlertsAuthorization } from './authorization/alerts_authorization';
import { AlertingAuthorization } from './authorization/alerting_authorization';
import { ActionsAuthorization } from '../../actions/server';
import { SavedObjectsErrorHelpers } from '../../../../src/core/server';
import { RetryForConflictsAttempts } from './lib/retry_if_conflicts';
@ -32,7 +32,7 @@ const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const authorization = alertingAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();
const kibanaVersion = 'v7.10.0';
@ -41,7 +41,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
authorization: (authorization as unknown) as AlertingAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',

View file

@ -20,34 +20,40 @@ import { AuthenticatedUser } from '../../../plugins/security/common/model';
import { securityMock } from '../../security/server/mocks';
import { PluginStartContract as ActionsStartContract } from '../../actions/server';
import { actionsMock, actionsAuthorizationMock } from '../../actions/server/mocks';
import { featuresPluginMock } from '../../features/server/mocks';
import { LegacyAuditLogger } from '../../security/server';
import { ALERTS_FEATURE_ID } from '../common';
import { eventLogMock } from '../../event_log/server/mocks';
import { alertingAuthorizationMock } from './authorization/alerting_authorization.mock';
import { alertingAuthorizationClientFactoryMock } from './alerting_authorization_client_factory.mock';
import { AlertingAuthorization } from './authorization';
import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory';
jest.mock('./alerts_client');
jest.mock('./authorization/alerts_authorization');
jest.mock('./authorization/alerting_authorization');
jest.mock('./authorization/audit_logger');
const savedObjectsClient = savedObjectsClientMock.create();
const savedObjectsService = savedObjectsServiceMock.createInternalStartContract();
const features = featuresPluginMock.createStart();
const securityPluginSetup = securityMock.createSetup();
const securityPluginStart = securityMock.createStart();
const alertsAuthorization = alertingAuthorizationMock.create();
const alertingAuthorizationClientFactory = alertingAuthorizationClientFactoryMock.createFactory();
const alertsClientFactoryParams: jest.Mocked<AlertsClientFactoryOpts> = {
logger: loggingSystemMock.create().get(),
taskManager: taskManagerMock.createStart(),
alertTypeRegistry: alertTypeRegistryMock.create(),
getSpaceId: jest.fn(),
getSpace: jest.fn(),
spaceIdToNamespace: jest.fn(),
encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(),
actions: actionsMock.createStart(),
features,
eventLog: eventLogMock.createStart(),
kibanaVersion: '7.10.0',
authorization: (alertingAuthorizationClientFactory as unknown) as AlertingAuthorizationClientFactory,
};
const fakeRequest = ({
app: {},
headers: {},
@ -82,8 +88,10 @@ test('creates an alerts client with proper constructor arguments when security i
factory.initialize({ securityPluginSetup, securityPluginStart, ...alertsClientFactoryParams });
const request = KibanaRequest.from(fakeRequest);
const { AlertsAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger');
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
alertingAuthorizationClientFactory.create.mockReturnValue(
(alertsAuthorization as unknown) as AlertingAuthorization
);
const logger = {
log: jest.fn(),
@ -97,18 +105,9 @@ test('creates an alerts client with proper constructor arguments when security i
includedHiddenTypes: ['alert', 'api_key_pending_invalidation'],
});
const { AlertsAuthorization } = jest.requireMock('./authorization/alerts_authorization');
expect(AlertsAuthorization).toHaveBeenCalledWith({
request,
authorization: securityPluginStart.authz,
alertTypeRegistry: alertsClientFactoryParams.alertTypeRegistry,
features: alertsClientFactoryParams.features,
auditLogger: expect.any(AlertsAuthorizationAuditLogger),
getSpace: expect.any(Function),
});
expect(AlertsAuthorizationAuditLogger).toHaveBeenCalledWith(logger);
expect(securityPluginSetup.audit.getLogger).toHaveBeenCalledWith(ALERTS_FEATURE_ID);
expect(alertingAuthorizationClientFactory.create).toHaveBeenCalledWith(request, [
ALERTS_FEATURE_ID,
]);
expect(alertsClientFactoryParams.actions.getActionsAuthorizationWithRequest).toHaveBeenCalledWith(
request
@ -116,7 +115,7 @@ test('creates an alerts client with proper constructor arguments when security i
expect(jest.requireMock('./alerts_client').AlertsClient).toHaveBeenCalledWith({
unsecuredSavedObjectsClient: savedObjectsClient,
authorization: expect.any(AlertsAuthorization),
authorization: alertsAuthorization,
actionsAuthorization,
logger: alertsClientFactoryParams.logger,
taskManager: alertsClientFactoryParams.taskManager,
@ -138,6 +137,9 @@ test('creates an alerts client with proper constructor arguments', async () => {
const request = KibanaRequest.from(fakeRequest);
savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient);
alertingAuthorizationClientFactory.create.mockReturnValue(
(alertsAuthorization as unknown) as AlertingAuthorization
);
factory.create(request, savedObjectsService);
@ -146,20 +148,13 @@ test('creates an alerts client with proper constructor arguments', async () => {
includedHiddenTypes: ['alert', 'api_key_pending_invalidation'],
});
const { AlertsAuthorization } = jest.requireMock('./authorization/alerts_authorization');
const { AlertsAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger');
expect(AlertsAuthorization).toHaveBeenCalledWith({
request,
authorization: undefined,
alertTypeRegistry: alertsClientFactoryParams.alertTypeRegistry,
features: alertsClientFactoryParams.features,
auditLogger: expect.any(AlertsAuthorizationAuditLogger),
getSpace: expect.any(Function),
});
expect(alertingAuthorizationClientFactory.create).toHaveBeenCalledWith(request, [
ALERTS_FEATURE_ID,
]);
expect(jest.requireMock('./alerts_client').AlertsClient).toHaveBeenCalledWith({
unsecuredSavedObjectsClient: savedObjectsClient,
authorization: expect.any(AlertsAuthorization),
authorization: alertsAuthorization,
actionsAuthorization,
logger: alertsClientFactoryParams.logger,
taskManager: alertsClientFactoryParams.taskManager,

View file

@ -13,17 +13,13 @@ import {
} from 'src/core/server';
import { PluginStartContract as ActionsPluginStartContract } from '../../actions/server';
import { AlertsClient } from './alerts_client';
import { ALERTS_FEATURE_ID } from '../common';
import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from './types';
import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server';
import { EncryptedSavedObjectsClient } from '../../encrypted_saved_objects/server';
import { TaskManagerStartContract } from '../../task_manager/server';
import { PluginStartContract as FeaturesPluginStart } from '../../features/server';
import { AlertsAuthorization } from './authorization/alerts_authorization';
import { AlertsAuthorizationAuditLogger } from './authorization/audit_logger';
import { Space } from '../../spaces/server';
import { IEventLogClientService } from '../../../plugins/event_log/server';
import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory';
import { ALERTS_FEATURE_ID } from '../common';
export interface AlertsClientFactoryOpts {
logger: Logger;
taskManager: TaskManagerStartContract;
@ -31,13 +27,12 @@ export interface AlertsClientFactoryOpts {
securityPluginSetup?: SecurityPluginSetup;
securityPluginStart?: SecurityPluginStart;
getSpaceId: (request: KibanaRequest) => string | undefined;
getSpace: (request: KibanaRequest) => Promise<Space | undefined>;
spaceIdToNamespace: SpaceIdToNamespaceFunction;
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
actions: ActionsPluginStartContract;
features: FeaturesPluginStart;
eventLog: IEventLogClientService;
kibanaVersion: PluginInitializerContext['env']['packageInfo']['version'];
authorization: AlertingAuthorizationClientFactory;
}
export class AlertsClientFactory {
@ -48,13 +43,12 @@ export class AlertsClientFactory {
private securityPluginSetup?: SecurityPluginSetup;
private securityPluginStart?: SecurityPluginStart;
private getSpaceId!: (request: KibanaRequest) => string | undefined;
private getSpace!: (request: KibanaRequest) => Promise<Space | undefined>;
private spaceIdToNamespace!: SpaceIdToNamespaceFunction;
private encryptedSavedObjectsClient!: EncryptedSavedObjectsClient;
private actions!: ActionsPluginStartContract;
private features!: FeaturesPluginStart;
private eventLog!: IEventLogClientService;
private kibanaVersion!: PluginInitializerContext['env']['packageInfo']['version'];
private authorization!: AlertingAuthorizationClientFactory;
public initialize(options: AlertsClientFactoryOpts) {
if (this.isInitialized) {
@ -63,7 +57,6 @@ export class AlertsClientFactory {
this.isInitialized = true;
this.logger = options.logger;
this.getSpaceId = options.getSpaceId;
this.getSpace = options.getSpace;
this.taskManager = options.taskManager;
this.alertTypeRegistry = options.alertTypeRegistry;
this.securityPluginSetup = options.securityPluginSetup;
@ -71,24 +64,18 @@ export class AlertsClientFactory {
this.spaceIdToNamespace = options.spaceIdToNamespace;
this.encryptedSavedObjectsClient = options.encryptedSavedObjectsClient;
this.actions = options.actions;
this.features = options.features;
this.eventLog = options.eventLog;
this.kibanaVersion = options.kibanaVersion;
this.authorization = options.authorization;
}
public create(request: KibanaRequest, savedObjects: SavedObjectsServiceStart): AlertsClient {
const { securityPluginSetup, securityPluginStart, actions, eventLog, features } = this;
const { securityPluginSetup, securityPluginStart, actions, eventLog } = this;
const spaceId = this.getSpaceId(request);
const authorization = new AlertsAuthorization({
authorization: securityPluginStart?.authz,
request,
getSpace: this.getSpace,
alertTypeRegistry: this.alertTypeRegistry,
features: features!,
auditLogger: new AlertsAuthorizationAuditLogger(
securityPluginSetup?.audit.getLogger(ALERTS_FEATURE_ID)
),
});
if (!this.authorization) {
throw new Error('AlertingAuthorizationClientFactory is not defined');
}
return new AlertsClient({
spaceId,
@ -100,7 +87,7 @@ export class AlertsClientFactory {
excludedWrappers: ['security'],
includedHiddenTypes: ['alert', 'api_key_pending_invalidation'],
}),
authorization,
authorization: this.authorization.create(request, [ALERTS_FEATURE_ID]),
actionsAuthorization: actions.getActionsAuthorizationWithRequest(request),
namespace: this.spaceIdToNamespace(spaceId),
encryptedSavedObjectsClient: this.encryptedSavedObjectsClient,

View file

@ -1,316 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AlertsAuthorization getFindAuthorizationFilter creates a filter based on the privileged types 1`] = `
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.alertTypeId",
},
Object {
"type": "literal",
"value": "myAppAlertType",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "alerts",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myOtherApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myAppWithSubFeature",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
],
"function": "or",
"type": "function",
},
],
"function": "and",
"type": "function",
},
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.alertTypeId",
},
Object {
"type": "literal",
"value": "myOtherAppAlertType",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "alerts",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myOtherApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myAppWithSubFeature",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
],
"function": "or",
"type": "function",
},
],
"function": "and",
"type": "function",
},
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.alertTypeId",
},
Object {
"type": "literal",
"value": "mySecondAppAlertType",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "alerts",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myOtherApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myAppWithSubFeature",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
],
"function": "or",
"type": "function",
},
],
"function": "and",
"type": "function",
},
],
"function": "or",
"type": "function",
}
`;

View file

@ -1,448 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`asFiltersByAlertTypeAndConsumer constructs filter for multiple alert types across authorized consumer 1`] = `
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.alertTypeId",
},
Object {
"type": "literal",
"value": "myAppAlertType",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "alerts",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myOtherApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myAppWithSubFeature",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
],
"function": "or",
"type": "function",
},
],
"function": "and",
"type": "function",
},
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.alertTypeId",
},
Object {
"type": "literal",
"value": "myOtherAppAlertType",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "alerts",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myOtherApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myAppWithSubFeature",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
],
"function": "or",
"type": "function",
},
],
"function": "and",
"type": "function",
},
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.alertTypeId",
},
Object {
"type": "literal",
"value": "mySecondAppAlertType",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "alerts",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myOtherApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myAppWithSubFeature",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
],
"function": "or",
"type": "function",
},
],
"function": "and",
"type": "function",
},
],
"function": "or",
"type": "function",
}
`;
exports[`asFiltersByAlertTypeAndConsumer constructs filter for single alert type with multiple authorized consumer 1`] = `
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.alertTypeId",
},
Object {
"type": "literal",
"value": "myAppAlertType",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "alerts",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myOtherApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
],
"function": "or",
"type": "function",
},
],
"function": "and",
"type": "function",
}
`;
exports[`asFiltersByAlertTypeAndConsumer constructs filter for single alert type with single authorized consumer 1`] = `
Object {
"arguments": Array [
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.alertTypeId",
},
Object {
"type": "literal",
"value": "myAppAlertType",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
Object {
"arguments": Array [
Object {
"type": "literal",
"value": "alert.attributes.consumer",
},
Object {
"type": "literal",
"value": "myApp",
},
Object {
"type": "literal",
"value": false,
},
],
"function": "is",
"type": "function",
},
],
"function": "and",
"type": "function",
}
`;

View file

@ -0,0 +1,27 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
import { AlertingAuthorization } from './alerting_authorization';
type Schema = PublicMethodsOf<AlertingAuthorization>;
export type AlertingAuthorizationMock = jest.Mocked<Schema>;
const createAlertingAuthorizationMock = () => {
const mocked: AlertingAuthorizationMock = {
ensureAuthorized: jest.fn(),
filterByRuleTypeAuthorization: jest.fn(),
getFindAuthorizationFilter: jest.fn(),
};
return mocked;
};
export const alertingAuthorizationMock: {
create: () => AlertingAuthorizationMock;
} = {
create: createAlertingAuthorizationMock,
};

View file

@ -8,20 +8,28 @@
import Boom from '@hapi/boom';
import { map, mapValues, fromPairs, has } from 'lodash';
import { KibanaRequest } from 'src/core/server';
import { ALERTS_FEATURE_ID } from '../../common';
import { AlertTypeRegistry } from '../types';
import { SecurityPluginSetup } from '../../../security/server';
import { RegistryAlertType } from '../alert_type_registry';
import { PluginStartContract as FeaturesPluginStart } from '../../../features/server';
import { AlertsAuthorizationAuditLogger, ScopeType } from './audit_logger';
import { AlertingAuthorizationAuditLogger, ScopeType } from './audit_logger';
import { Space } from '../../../spaces/server';
import { asFiltersByAlertTypeAndConsumer } from './alerts_authorization_kuery';
import {
asFiltersByRuleTypeAndConsumer,
AlertingAuthorizationFilterOpts,
} from './alerting_authorization_kuery';
import { KueryNode } from '../../../../../src/plugins/data/server';
import { JsonObject } from '../../../../../src/plugins/kibana_utils/common';
export enum AlertingAuthorizationEntity {
Rule = 'rule',
Alert = 'alert',
}
export enum ReadOperations {
Get = 'get',
GetAlertState = 'getAlertState',
GetAlertInstanceSummary = 'getAlertInstanceSummary',
GetRuleState = 'getRuleState',
GetAlertSummary = 'getAlertSummary',
Find = 'find',
}
@ -34,8 +42,15 @@ export enum WriteOperations {
Disable = 'disable',
MuteAll = 'muteAll',
UnmuteAll = 'unmuteAll',
MuteInstance = 'muteInstance',
UnmuteInstance = 'unmuteInstance',
MuteAlert = 'muteAlert',
UnmuteAlert = 'unmuteAlert',
}
export interface EnsureAuthorizedOpts {
ruleTypeId: string;
consumer: string;
operation: ReadOperations | WriteOperations;
entity: AlertingAuthorizationEntity;
}
interface HasPrivileges {
@ -48,23 +63,24 @@ export interface RegistryAlertTypeWithAuth extends RegistryAlertType {
}
type IsAuthorizedAtProducerLevel = boolean;
export interface ConstructorOptions {
alertTypeRegistry: AlertTypeRegistry;
request: KibanaRequest;
features: FeaturesPluginStart;
getSpace: (request: KibanaRequest) => Promise<Space | undefined>;
auditLogger: AlertsAuthorizationAuditLogger;
auditLogger: AlertingAuthorizationAuditLogger;
exemptConsumerIds: string[];
authorization?: SecurityPluginSetup['authz'];
}
export class AlertsAuthorization {
export class AlertingAuthorization {
private readonly alertTypeRegistry: AlertTypeRegistry;
private readonly request: KibanaRequest;
private readonly authorization?: SecurityPluginSetup['authz'];
private readonly auditLogger: AlertsAuthorizationAuditLogger;
private readonly auditLogger: AlertingAuthorizationAuditLogger;
private readonly featuresIds: Promise<Set<string>>;
private readonly allPossibleConsumers: Promise<AuthorizedConsumers>;
private readonly exemptConsumerIds: string[];
constructor({
alertTypeRegistry,
@ -73,12 +89,18 @@ export class AlertsAuthorization {
features,
auditLogger,
getSpace,
exemptConsumerIds,
}: ConstructorOptions) {
this.request = request;
this.authorization = authorization;
this.alertTypeRegistry = alertTypeRegistry;
this.auditLogger = auditLogger;
// List of consumer ids that are exempt from privilege check. This should be used sparingly.
// An example of this is the Rules Management `consumer` as we don't want to have to
// manually authorize each rule type in the management UI.
this.exemptConsumerIds = exemptConsumerIds;
this.featuresIds = getSpace(request)
.then((maybeSpace) => new Set(maybeSpace?.disabledFeatures ?? []))
.then(
@ -104,7 +126,7 @@ export class AlertsAuthorization {
this.allPossibleConsumers = this.featuresIds.then((featuresIds) =>
featuresIds.size
? asAuthorizedConsumers([ALERTS_FEATURE_ID, ...featuresIds], {
? asAuthorizedConsumers([...this.exemptConsumerIds, ...featuresIds], {
read: true,
all: true,
})
@ -116,29 +138,29 @@ export class AlertsAuthorization {
return this.authorization?.mode?.useRbacForRequest(this.request) ?? false;
}
public async ensureAuthorized(
alertTypeId: string,
consumer: string,
operation: ReadOperations | WriteOperations
) {
public async ensureAuthorized({ ruleTypeId, consumer, operation, entity }: EnsureAuthorizedOpts) {
const { authorization } = this;
const isAvailableConsumer = has(await this.allPossibleConsumers, consumer);
if (authorization && this.shouldCheckAuthorization()) {
const alertType = this.alertTypeRegistry.get(alertTypeId);
const ruleType = this.alertTypeRegistry.get(ruleTypeId);
const requiredPrivilegesByScope = {
consumer: authorization.actions.alerting.get(alertTypeId, consumer, operation),
producer: authorization.actions.alerting.get(alertTypeId, alertType.producer, operation),
consumer: authorization.actions.alerting.get(ruleTypeId, consumer, entity, operation),
producer: authorization.actions.alerting.get(
ruleTypeId,
ruleType.producer,
entity,
operation
),
};
// We special case the Alerts Management `consumer` as we don't want to have to
// manually authorize each alert type in the management UI
const shouldAuthorizeConsumer = consumer !== ALERTS_FEATURE_ID;
// Skip authorizing consumer if it is in the list of exempt consumer ids
const shouldAuthorizeConsumer = !this.exemptConsumerIds.includes(consumer);
const checkPrivileges = authorization.checkPrivilegesDynamicallyWithRequest(this.request);
const { hasAllRequested, username, privileges } = await checkPrivileges({
kibana:
shouldAuthorizeConsumer && consumer !== alertType.producer
shouldAuthorizeConsumer && consumer !== ruleType.producer
? [
// check for access at consumer level
requiredPrivilegesByScope.consumer,
@ -146,8 +168,8 @@ export class AlertsAuthorization {
requiredPrivilegesByScope.producer,
]
: [
// skip consumer privilege checks under `alerts` as all alert types can
// be created under `alerts` if you have producer level privileges
// skip consumer privilege checks for exempt consumer ids as all rule types can
// be created for exempt consumers if user has producer level privileges
requiredPrivilegesByScope.producer,
],
});
@ -161,23 +183,25 @@ export class AlertsAuthorization {
* This check will ensure we don't accidentally let these through
*/
throw Boom.forbidden(
this.auditLogger.alertsAuthorizationFailure(
this.auditLogger.logAuthorizationFailure(
username,
alertTypeId,
ruleTypeId,
ScopeType.Consumer,
consumer,
operation
operation,
entity
)
);
}
if (hasAllRequested) {
this.auditLogger.alertsAuthorizationSuccess(
this.auditLogger.logAuthorizationSuccess(
username,
alertTypeId,
ruleTypeId,
ScopeType.Consumer,
consumer,
operation
operation,
entity
);
} else {
const authorizedPrivileges = map(
@ -192,84 +216,89 @@ export class AlertsAuthorization {
const [unauthorizedScopeType, unauthorizedScope] =
shouldAuthorizeConsumer && unauthorizedScopes.consumer
? [ScopeType.Consumer, consumer]
: [ScopeType.Producer, alertType.producer];
: [ScopeType.Producer, ruleType.producer];
throw Boom.forbidden(
this.auditLogger.alertsAuthorizationFailure(
this.auditLogger.logAuthorizationFailure(
username,
alertTypeId,
ruleTypeId,
unauthorizedScopeType,
unauthorizedScope,
operation
operation,
entity
)
);
}
} else if (!isAvailableConsumer) {
throw Boom.forbidden(
this.auditLogger.alertsAuthorizationFailure(
this.auditLogger.logAuthorizationFailure(
'',
alertTypeId,
ruleTypeId,
ScopeType.Consumer,
consumer,
operation
operation,
entity
)
);
}
}
public async getFindAuthorizationFilter(): Promise<{
filter?: KueryNode;
ensureAlertTypeIsAuthorized: (alertTypeId: string, consumer: string) => void;
public async getFindAuthorizationFilter(
authorizationEntity: AlertingAuthorizationEntity,
filterOpts: AlertingAuthorizationFilterOpts
): Promise<{
filter?: KueryNode | JsonObject;
ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, auth: string) => void;
logSuccessfulAuthorization: () => void;
}> {
if (this.authorization && this.shouldCheckAuthorization()) {
const {
username,
authorizedAlertTypes,
} = await this.augmentAlertTypesWithAuthorization(this.alertTypeRegistry.list(), [
ReadOperations.Find,
]);
const { username, authorizedRuleTypes } = await this.augmentRuleTypesWithAuthorization(
this.alertTypeRegistry.list(),
[ReadOperations.Find],
authorizationEntity
);
if (!authorizedAlertTypes.size) {
if (!authorizedRuleTypes.size) {
throw Boom.forbidden(
this.auditLogger.alertsUnscopedAuthorizationFailure(username!, 'find')
this.auditLogger.logUnscopedAuthorizationFailure(username!, 'find', authorizationEntity)
);
}
const authorizedAlertTypeIdsToConsumers = new Set<string>(
[...authorizedAlertTypes].reduce<string[]>((alertTypeIdConsumerPairs, alertType) => {
for (const consumer of Object.keys(alertType.authorizedConsumers)) {
alertTypeIdConsumerPairs.push(`${alertType.id}/${consumer}`);
const authorizedRuleTypeIdsToConsumers = new Set<string>(
[...authorizedRuleTypes].reduce<string[]>((ruleTypeIdConsumerPairs, ruleType) => {
for (const consumer of Object.keys(ruleType.authorizedConsumers)) {
ruleTypeIdConsumerPairs.push(`${ruleType.id}/${consumer}/${authorizationEntity}`);
}
return alertTypeIdConsumerPairs;
return ruleTypeIdConsumerPairs;
}, [])
);
const authorizedEntries: Map<string, Set<string>> = new Map();
return {
filter: asFiltersByAlertTypeAndConsumer(authorizedAlertTypes),
ensureAlertTypeIsAuthorized: (alertTypeId: string, consumer: string) => {
if (!authorizedAlertTypeIdsToConsumers.has(`${alertTypeId}/${consumer}`)) {
filter: asFiltersByRuleTypeAndConsumer(authorizedRuleTypes, filterOpts),
ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, authType: string) => {
if (!authorizedRuleTypeIdsToConsumers.has(`${ruleTypeId}/${consumer}/${authType}`)) {
throw Boom.forbidden(
this.auditLogger.alertsAuthorizationFailure(
this.auditLogger.logAuthorizationFailure(
username!,
alertTypeId,
ruleTypeId,
ScopeType.Consumer,
consumer,
'find'
'find',
authorizationEntity
)
);
} else {
if (authorizedEntries.has(alertTypeId)) {
authorizedEntries.get(alertTypeId)!.add(consumer);
if (authorizedEntries.has(ruleTypeId)) {
authorizedEntries.get(ruleTypeId)!.add(consumer);
} else {
authorizedEntries.set(alertTypeId, new Set([consumer]));
authorizedEntries.set(ruleTypeId, new Set([consumer]));
}
}
},
logSuccessfulAuthorization: () => {
if (authorizedEntries.size) {
this.auditLogger.alertsBulkAuthorizationSuccess(
this.auditLogger.logBulkAuthorizationSuccess(
username!,
[...authorizedEntries.entries()].reduce<Array<[string, string]>>(
(authorizedPairs, [alertTypeId, consumers]) => {
@ -281,36 +310,40 @@ export class AlertsAuthorization {
[]
),
ScopeType.Consumer,
'find'
'find',
authorizationEntity
);
}
},
};
}
return {
ensureAlertTypeIsAuthorized: (alertTypeId: string, consumer: string) => {},
ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, authType: string) => {},
logSuccessfulAuthorization: () => {},
};
}
public async filterByAlertTypeAuthorization(
alertTypes: Set<RegistryAlertType>,
operations: Array<ReadOperations | WriteOperations>
public async filterByRuleTypeAuthorization(
ruleTypes: Set<RegistryAlertType>,
operations: Array<ReadOperations | WriteOperations>,
authorizationEntity: AlertingAuthorizationEntity
): Promise<Set<RegistryAlertTypeWithAuth>> {
const { authorizedAlertTypes } = await this.augmentAlertTypesWithAuthorization(
alertTypes,
operations
const { authorizedRuleTypes } = await this.augmentRuleTypesWithAuthorization(
ruleTypes,
operations,
authorizationEntity
);
return authorizedAlertTypes;
return authorizedRuleTypes;
}
private async augmentAlertTypesWithAuthorization(
alertTypes: Set<RegistryAlertType>,
operations: Array<ReadOperations | WriteOperations>
private async augmentRuleTypesWithAuthorization(
ruleTypes: Set<RegistryAlertType>,
operations: Array<ReadOperations | WriteOperations>,
authorizationEntity: AlertingAuthorizationEntity
): Promise<{
username?: string;
hasAllRequested: boolean;
authorizedAlertTypes: Set<RegistryAlertTypeWithAuth>;
authorizedRuleTypes: Set<RegistryAlertTypeWithAuth>;
}> {
const featuresIds = await this.featuresIds;
if (this.authorization && this.shouldCheckAuthorization()) {
@ -318,74 +351,76 @@ export class AlertsAuthorization {
this.request
);
// add an empty `authorizedConsumers` array on each alertType
const alertTypesWithAuthorization = this.augmentWithAuthorizedConsumers(alertTypes, {});
// add an empty `authorizedConsumers` array on each ruleType
const ruleTypesWithAuthorization = this.augmentWithAuthorizedConsumers(ruleTypes, {});
// map from privilege to alertType which we can refer back to when analyzing the result
// map from privilege to ruleType which we can refer back to when analyzing the result
// of checkPrivileges
const privilegeToAlertType = new Map<
const privilegeToRuleType = new Map<
string,
[RegistryAlertTypeWithAuth, string, HasPrivileges, IsAuthorizedAtProducerLevel]
>();
// as we can't ask ES for the user's individual privileges we need to ask for each feature
// and alertType in the system whether this user has this privilege
for (const alertType of alertTypesWithAuthorization) {
// and ruleType in the system whether this user has this privilege
for (const ruleType of ruleTypesWithAuthorization) {
for (const feature of featuresIds) {
for (const operation of operations) {
privilegeToAlertType.set(
this.authorization!.actions.alerting.get(alertType.id, feature, operation),
[
alertType,
privilegeToRuleType.set(
this.authorization!.actions.alerting.get(
ruleType.id,
feature,
hasPrivilegeByOperation(operation),
alertType.producer === feature,
]
authorizationEntity,
operation
),
[ruleType, feature, hasPrivilegeByOperation(operation), ruleType.producer === feature]
);
}
}
}
const { username, hasAllRequested, privileges } = await checkPrivileges({
kibana: [...privilegeToAlertType.keys()],
kibana: [...privilegeToRuleType.keys()],
});
return {
username,
hasAllRequested,
authorizedAlertTypes: hasAllRequested
authorizedRuleTypes: hasAllRequested
? // has access to all features
this.augmentWithAuthorizedConsumers(alertTypes, await this.allPossibleConsumers)
this.augmentWithAuthorizedConsumers(ruleTypes, await this.allPossibleConsumers)
: // only has some of the required privileges
privileges.kibana.reduce((authorizedAlertTypes, { authorized, privilege }) => {
if (authorized && privilegeToAlertType.has(privilege)) {
privileges.kibana.reduce((authorizedRuleTypes, { authorized, privilege }) => {
if (authorized && privilegeToRuleType.has(privilege)) {
const [
alertType,
ruleType,
feature,
hasPrivileges,
isAuthorizedAtProducerLevel,
] = privilegeToAlertType.get(privilege)!;
alertType.authorizedConsumers[feature] = mergeHasPrivileges(
] = privilegeToRuleType.get(privilege)!;
ruleType.authorizedConsumers[feature] = mergeHasPrivileges(
hasPrivileges,
alertType.authorizedConsumers[feature]
ruleType.authorizedConsumers[feature]
);
if (isAuthorizedAtProducerLevel) {
// granting privileges under the producer automatically authorized the Alerts Management UI as well
alertType.authorizedConsumers[ALERTS_FEATURE_ID] = mergeHasPrivileges(
hasPrivileges,
alertType.authorizedConsumers[ALERTS_FEATURE_ID]
);
if (isAuthorizedAtProducerLevel && this.exemptConsumerIds.length > 0) {
// granting privileges under the producer automatically authorized exempt consumer IDs as well
this.exemptConsumerIds.forEach((exemptId: string) => {
ruleType.authorizedConsumers[exemptId] = mergeHasPrivileges(
hasPrivileges,
ruleType.authorizedConsumers[exemptId]
);
});
}
authorizedAlertTypes.add(alertType);
authorizedRuleTypes.add(ruleType);
}
return authorizedAlertTypes;
return authorizedRuleTypes;
}, new Set<RegistryAlertTypeWithAuth>()),
};
} else {
return {
hasAllRequested: true,
authorizedAlertTypes: this.augmentWithAuthorizedConsumers(
new Set([...alertTypes].filter((alertType) => featuresIds.has(alertType.producer))),
authorizedRuleTypes: this.augmentWithAuthorizedConsumers(
new Set([...ruleTypes].filter((ruleType) => featuresIds.has(ruleType.producer))),
await this.allPossibleConsumers
),
};
@ -393,12 +428,12 @@ export class AlertsAuthorization {
}
private augmentWithAuthorizedConsumers(
alertTypes: Set<RegistryAlertType>,
ruleTypes: Set<RegistryAlertType>,
authorizedConsumers: AuthorizedConsumers
): Set<RegistryAlertTypeWithAuth> {
return new Set(
Array.from(alertTypes).map((alertType) => ({
...alertType,
Array.from(ruleTypes).map((ruleType) => ({
...ruleType,
authorizedConsumers: { ...authorizedConsumers },
}))
);

View file

@ -0,0 +1,508 @@
/*
* 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 { RecoveredActionGroup } from '../../common';
import {
AlertingAuthorizationFilterType,
asFiltersByRuleTypeAndConsumer,
ensureFieldIsSafeForQuery,
} from './alerting_authorization_kuery';
import { esKuery } from '../../../../../src/plugins/data/server';
describe('asKqlFiltersByRuleTypeAndConsumer', () => {
test('constructs KQL filter for single rule type with single authorized consumer', async () => {
expect(
asFiltersByRuleTypeAndConsumer(
new Set([
{
actionGroups: [],
defaultActionGroupId: 'default',
recoveryActionGroup: RecoveredActionGroup,
id: 'myAppAlertType',
name: 'myAppAlertType',
producer: 'myApp',
minimumLicenseRequired: 'basic',
authorizedConsumers: {
myApp: { read: true, all: true },
},
enabledInLicense: true,
},
]),
{
type: AlertingAuthorizationFilterType.KQL,
fieldNames: {
ruleTypeId: 'path.to.rule.id',
consumer: 'consumer-field',
},
}
)
).toEqual(
esKuery.fromKueryExpression(`((path.to.rule.id:myAppAlertType and consumer-field:(myApp)))`)
);
});
test('constructs KQL filter for single rule type with multiple authorized consumers', async () => {
expect(
asFiltersByRuleTypeAndConsumer(
new Set([
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myAppAlertType',
name: 'myAppAlertType',
producer: 'myApp',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
},
enabledInLicense: true,
},
]),
{
type: AlertingAuthorizationFilterType.KQL,
fieldNames: {
ruleTypeId: 'path.to.rule.id',
consumer: 'consumer-field',
},
}
)
).toEqual(
esKuery.fromKueryExpression(
`((path.to.rule.id:myAppAlertType and consumer-field:(alerts or myApp or myOtherApp)))`
)
);
});
test('constructs KQL filter for multiple rule types across authorized consumer', async () => {
expect(
asFiltersByRuleTypeAndConsumer(
new Set([
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myAppAlertType',
name: 'myAppAlertType',
producer: 'myApp',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
myAppWithSubFeature: { read: true, all: true },
},
enabledInLicense: true,
},
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myOtherAppAlertType',
name: 'myOtherAppAlertType',
producer: 'alerts',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
myAppWithSubFeature: { read: true, all: true },
},
enabledInLicense: true,
},
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'mySecondAppAlertType',
name: 'mySecondAppAlertType',
producer: 'myApp',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
myAppWithSubFeature: { read: true, all: true },
},
enabledInLicense: true,
},
]),
{
type: AlertingAuthorizationFilterType.KQL,
fieldNames: {
ruleTypeId: 'path.to.rule.id',
consumer: 'consumer-field',
},
}
)
).toEqual(
esKuery.fromKueryExpression(
`((path.to.rule.id:myAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule.id:myOtherAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule.id:mySecondAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)))`
)
);
});
});
describe('asEsDslFiltersByRuleTypeAndConsumer', () => {
test('constructs ES DSL filter for single rule type with single authorized consumer', async () => {
expect(
asFiltersByRuleTypeAndConsumer(
new Set([
{
actionGroups: [],
defaultActionGroupId: 'default',
recoveryActionGroup: RecoveredActionGroup,
id: 'myAppAlertType',
name: 'myAppAlertType',
producer: 'myApp',
minimumLicenseRequired: 'basic',
authorizedConsumers: {
myApp: { read: true, all: true },
},
enabledInLicense: true,
},
]),
{
type: AlertingAuthorizationFilterType.ESDSL,
fieldNames: {
ruleTypeId: 'path.to.rule.id',
consumer: 'consumer-field',
},
}
)
).toEqual({
bool: {
filter: [
{
bool: {
should: [
{
match: {
'path.to.rule.id': 'myAppAlertType',
},
},
],
minimum_should_match: 1,
},
},
{
bool: {
should: [
{
match: {
'consumer-field': 'myApp',
},
},
],
minimum_should_match: 1,
},
},
],
},
});
});
test('constructs ES DSL filter for single rule type with multiple authorized consumers', async () => {
expect(
asFiltersByRuleTypeAndConsumer(
new Set([
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myAppAlertType',
name: 'myAppAlertType',
producer: 'myApp',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
},
enabledInLicense: true,
},
]),
{
type: AlertingAuthorizationFilterType.ESDSL,
fieldNames: {
ruleTypeId: 'path.to.rule.id',
consumer: 'consumer-field',
},
}
)
).toEqual({
bool: {
filter: [
{
bool: {
should: [{ match: { 'path.to.rule.id': 'myAppAlertType' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [
{
bool: {
should: [{ match: { 'consumer-field': 'alerts' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [{ match: { 'consumer-field': 'myApp' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [{ match: { 'consumer-field': 'myOtherApp' } }],
minimum_should_match: 1,
},
},
],
minimum_should_match: 1,
},
},
],
},
});
});
test('constructs ES DSL filter for multiple rule types across authorized consumer', async () => {
expect(
asFiltersByRuleTypeAndConsumer(
new Set([
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myAppAlertType',
name: 'myAppAlertType',
producer: 'myApp',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
myAppWithSubFeature: { read: true, all: true },
},
enabledInLicense: true,
},
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myOtherAppAlertType',
name: 'myOtherAppAlertType',
producer: 'alerts',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
myAppWithSubFeature: { read: true, all: true },
},
enabledInLicense: true,
},
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'mySecondAppAlertType',
name: 'mySecondAppAlertType',
producer: 'myApp',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
myAppWithSubFeature: { read: true, all: true },
},
enabledInLicense: true,
},
]),
{
type: AlertingAuthorizationFilterType.ESDSL,
fieldNames: {
ruleTypeId: 'path.to.rule.id',
consumer: 'consumer-field',
},
}
)
).toEqual({
bool: {
should: [
{
bool: {
filter: [
{
bool: {
should: [{ match: { 'path.to.rule.id': 'myAppAlertType' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [
{
bool: {
should: [{ match: { 'consumer-field': 'alerts' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [{ match: { 'consumer-field': 'myApp' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [{ match: { 'consumer-field': 'myOtherApp' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [{ match: { 'consumer-field': 'myAppWithSubFeature' } }],
minimum_should_match: 1,
},
},
],
minimum_should_match: 1,
},
},
],
},
},
{
bool: {
filter: [
{
bool: {
should: [{ match: { 'path.to.rule.id': 'myOtherAppAlertType' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [
{
bool: {
should: [{ match: { 'consumer-field': 'alerts' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [{ match: { 'consumer-field': 'myApp' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [{ match: { 'consumer-field': 'myOtherApp' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [{ match: { 'consumer-field': 'myAppWithSubFeature' } }],
minimum_should_match: 1,
},
},
],
minimum_should_match: 1,
},
},
],
},
},
{
bool: {
filter: [
{
bool: {
should: [{ match: { 'path.to.rule.id': 'mySecondAppAlertType' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [
{
bool: {
should: [{ match: { 'consumer-field': 'alerts' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [{ match: { 'consumer-field': 'myApp' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [{ match: { 'consumer-field': 'myOtherApp' } }],
minimum_should_match: 1,
},
},
{
bool: {
should: [{ match: { 'consumer-field': 'myAppWithSubFeature' } }],
minimum_should_match: 1,
},
},
],
minimum_should_match: 1,
},
},
],
},
},
],
minimum_should_match: 1,
},
});
});
});
describe('ensureFieldIsSafeForQuery', () => {
test('throws if field contains character that isnt safe in a KQL query', () => {
expect(() => ensureFieldIsSafeForQuery('id', 'alert-*')).toThrowError(
`expected id not to include invalid character: *`
);
expect(() => ensureFieldIsSafeForQuery('id', '<=""')).toThrowError(
`expected id not to include invalid character: <=`
);
expect(() => ensureFieldIsSafeForQuery('id', '>=""')).toThrowError(
`expected id not to include invalid character: >=`
);
expect(() => ensureFieldIsSafeForQuery('id', '1 or alertid:123')).toThrowError(
`expected id not to include whitespace and invalid character: :`
);
expect(() => ensureFieldIsSafeForQuery('id', ') or alertid:123')).toThrowError(
`expected id not to include whitespace and invalid characters: ), :`
);
expect(() => ensureFieldIsSafeForQuery('id', 'some space')).toThrowError(
`expected id not to include whitespace`
);
});
test('doesnt throws if field is safe as part of a KQL query', () => {
expect(() => ensureFieldIsSafeForQuery('id', '123-0456-678')).not.toThrow();
});
});

View file

@ -0,0 +1,80 @@
/*
* 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 { remove } from 'lodash';
import { JsonObject } from '../../../../../src/plugins/kibana_utils/common';
import { nodeBuilder, EsQueryConfig } from '../../../../../src/plugins/data/common';
import { toElasticsearchQuery } from '../../../../../src/plugins/data/common/es_query';
import { KueryNode } from '../../../../../src/plugins/data/server';
import { RegistryAlertTypeWithAuth } from './alerting_authorization';
export enum AlertingAuthorizationFilterType {
KQL = 'kql',
ESDSL = 'dsl',
}
export interface AlertingAuthorizationFilterOpts {
type: AlertingAuthorizationFilterType;
fieldNames: AlertingAuthorizationFilterFieldNames;
}
interface AlertingAuthorizationFilterFieldNames {
ruleTypeId: string;
consumer: string;
}
const esQueryConfig: EsQueryConfig = {
allowLeadingWildcards: true,
dateFormatTZ: 'Zulu',
ignoreFilterIfFieldNotInIndex: false,
queryStringOptions: { analyze_wildcard: true },
};
export function asFiltersByRuleTypeAndConsumer(
ruleTypes: Set<RegistryAlertTypeWithAuth>,
opts: AlertingAuthorizationFilterOpts
): KueryNode | JsonObject {
const kueryNode = nodeBuilder.or(
Array.from(ruleTypes).reduce<KueryNode[]>((filters, { id, authorizedConsumers }) => {
ensureFieldIsSafeForQuery('ruleTypeId', id);
filters.push(
nodeBuilder.and([
nodeBuilder.is(opts.fieldNames.ruleTypeId, id),
nodeBuilder.or(
Object.keys(authorizedConsumers).map((consumer) => {
ensureFieldIsSafeForQuery('consumer', consumer);
return nodeBuilder.is(opts.fieldNames.consumer, consumer);
})
),
])
);
return filters;
}, [])
);
if (opts.type === AlertingAuthorizationFilterType.ESDSL) {
return toElasticsearchQuery(kueryNode, undefined, esQueryConfig);
}
return kueryNode;
}
export function ensureFieldIsSafeForQuery(field: string, value: string): boolean {
const invalid = value.match(/([>=<\*:()]+|\s+)/g);
if (invalid) {
const whitespace = remove(invalid, (chars) => chars.trim().length === 0);
const errors = [];
if (whitespace.length) {
errors.push(`whitespace`);
}
if (invalid.length) {
errors.push(`invalid character${invalid.length > 1 ? `s` : ``}: ${invalid?.join(`, `)}`);
}
throw new Error(`expected ${field} not to include ${errors.join(' and ')}`);
}
return true;
}

View file

@ -1,27 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
import { AlertsAuthorization } from './alerts_authorization';
type Schema = PublicMethodsOf<AlertsAuthorization>;
export type AlertsAuthorizationMock = jest.Mocked<Schema>;
const createAlertsAuthorizationMock = () => {
const mocked: AlertsAuthorizationMock = {
ensureAuthorized: jest.fn(),
filterByAlertTypeAuthorization: jest.fn(),
getFindAuthorizationFilter: jest.fn(),
};
return mocked;
};
export const alertsAuthorizationMock: {
create: () => AlertsAuthorizationMock;
} = {
create: createAlertsAuthorizationMock,
};

View file

@ -1,170 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { RecoveredActionGroup } from '../../common';
import {
asFiltersByAlertTypeAndConsumer,
ensureFieldIsSafeForQuery,
} from './alerts_authorization_kuery';
describe('asFiltersByAlertTypeAndConsumer', () => {
test('constructs filter for single alert type with single authorized consumer', async () => {
expect(
asFiltersByAlertTypeAndConsumer(
new Set([
{
actionGroups: [],
defaultActionGroupId: 'default',
recoveryActionGroup: RecoveredActionGroup,
id: 'myAppAlertType',
name: 'myAppAlertType',
producer: 'myApp',
minimumLicenseRequired: 'basic',
authorizedConsumers: {
myApp: { read: true, all: true },
},
enabledInLicense: true,
},
])
)
).toMatchSnapshot();
// TODO: once issue https://github.com/elastic/kibana/issues/89473 is
// resolved, we can start using this code again instead of toMatchSnapshot()
// ).toEqual(
// esKuery.fromKueryExpression(
// `((alert.attributes.alertTypeId:myAppAlertType and alert.attributes.consumer:(myApp)))`
// )
// );
});
test('constructs filter for single alert type with multiple authorized consumer', async () => {
expect(
asFiltersByAlertTypeAndConsumer(
new Set([
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myAppAlertType',
name: 'myAppAlertType',
producer: 'myApp',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
},
enabledInLicense: true,
},
])
)
).toMatchSnapshot();
// TODO: once issue https://github.com/elastic/kibana/issues/89473 is
// resolved, we can start using this code again, instead of toMatchSnapshot():
// ).toEqual(
// esKuery.fromKueryExpression(
// `((alert.attributes.alertTypeId:myAppAlertType and alert.attributes.consumer:(alerts or myApp or myOtherApp)))`
// )
// );
});
test('constructs filter for multiple alert types across authorized consumer', async () => {
expect(
asFiltersByAlertTypeAndConsumer(
new Set([
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myAppAlertType',
name: 'myAppAlertType',
producer: 'myApp',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
myAppWithSubFeature: { read: true, all: true },
},
enabledInLicense: true,
},
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myOtherAppAlertType',
name: 'myOtherAppAlertType',
producer: 'alerts',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
myAppWithSubFeature: { read: true, all: true },
},
enabledInLicense: true,
},
{
actionGroups: [],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'mySecondAppAlertType',
name: 'mySecondAppAlertType',
producer: 'myApp',
authorizedConsumers: {
alerts: { read: true, all: true },
myApp: { read: true, all: true },
myOtherApp: { read: true, all: true },
myAppWithSubFeature: { read: true, all: true },
},
enabledInLicense: true,
},
])
)
).toMatchSnapshot();
// TODO: once issue https://github.com/elastic/kibana/issues/89473 is
// resolved, we can start using this code again, instead of toMatchSnapshot():
// ).toEqual(
// esKuery.fromKueryExpression(
// `((alert.attributes.alertTypeId:myAppAlertType and alert.attributes.consumer:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (alert.attributes.alertTypeId:myOtherAppAlertType and alert.attributes.consumer:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (alert.attributes.alertTypeId:mySecondAppAlertType and alert.attributes.consumer:(alerts or myApp or myOtherApp or myAppWithSubFeature)))`
// )
// );
});
});
describe('ensureFieldIsSafeForQuery', () => {
test('throws if field contains character that isnt safe in a KQL query', () => {
expect(() => ensureFieldIsSafeForQuery('id', 'alert-*')).toThrowError(
`expected id not to include invalid character: *`
);
expect(() => ensureFieldIsSafeForQuery('id', '<=""')).toThrowError(
`expected id not to include invalid character: <=`
);
expect(() => ensureFieldIsSafeForQuery('id', '>=""')).toThrowError(
`expected id not to include invalid character: >=`
);
expect(() => ensureFieldIsSafeForQuery('id', '1 or alertid:123')).toThrowError(
`expected id not to include whitespace and invalid character: :`
);
expect(() => ensureFieldIsSafeForQuery('id', ') or alertid:123')).toThrowError(
`expected id not to include whitespace and invalid characters: ), :`
);
expect(() => ensureFieldIsSafeForQuery('id', 'some space')).toThrowError(
`expected id not to include whitespace`
);
});
test('doesnt throws if field is safe as part of a KQL query', () => {
expect(() => ensureFieldIsSafeForQuery('id', '123-0456-678')).not.toThrow();
});
});

View file

@ -1,49 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { remove } from 'lodash';
import { nodeBuilder } from '../../../../../src/plugins/data/common';
import { KueryNode } from '../../../../../src/plugins/data/server';
import { RegistryAlertTypeWithAuth } from './alerts_authorization';
export function asFiltersByAlertTypeAndConsumer(
alertTypes: Set<RegistryAlertTypeWithAuth>
): KueryNode {
return nodeBuilder.or(
Array.from(alertTypes).reduce<KueryNode[]>((filters, { id, authorizedConsumers }) => {
ensureFieldIsSafeForQuery('alertTypeId', id);
filters.push(
nodeBuilder.and([
nodeBuilder.is(`alert.attributes.alertTypeId`, id),
nodeBuilder.or(
Object.keys(authorizedConsumers).map((consumer) => {
ensureFieldIsSafeForQuery('consumer', consumer);
return nodeBuilder.is(`alert.attributes.consumer`, consumer);
})
),
])
);
return filters;
}, [])
);
}
export function ensureFieldIsSafeForQuery(field: string, value: string): boolean {
const invalid = value.match(/([>=<\*:()]+|\s+)/g);
if (invalid) {
const whitespace = remove(invalid, (chars) => chars.trim().length === 0);
const errors = [];
if (whitespace.length) {
errors.push(`whitespace`);
}
if (invalid.length) {
errors.push(`invalid character${invalid.length > 1 ? `s` : ``}: ${invalid?.join(`, `)}`);
}
throw new Error(`expected ${field} not to include ${errors.join(' and ')}`);
}
return true;
}

View file

@ -5,21 +5,21 @@
* 2.0.
*/
import { AlertsAuthorizationAuditLogger } from './audit_logger';
import { AlertingAuthorizationAuditLogger } from './audit_logger';
const createAlertsAuthorizationAuditLoggerMock = () => {
const createAlertingAuthorizationAuditLoggerMock = () => {
const mocked = ({
getAuthorizationMessage: jest.fn(),
alertsAuthorizationFailure: jest.fn(),
alertsUnscopedAuthorizationFailure: jest.fn(),
alertsAuthorizationSuccess: jest.fn(),
alertsBulkAuthorizationSuccess: jest.fn(),
} as unknown) as jest.Mocked<AlertsAuthorizationAuditLogger>;
logAuthorizationFailure: jest.fn(),
logUnscopedAuthorizationFailure: jest.fn(),
logAuthorizationSuccess: jest.fn(),
logBulkAuthorizationSuccess: jest.fn(),
} as unknown) as jest.Mocked<AlertingAuthorizationAuditLogger>;
return mocked;
};
export const alertsAuthorizationAuditLoggerMock: {
create: () => jest.Mocked<AlertsAuthorizationAuditLogger>;
export const alertingAuthorizationAuditLoggerMock: {
create: () => jest.Mocked<AlertingAuthorizationAuditLogger>;
} = {
create: createAlertsAuthorizationAuditLoggerMock,
create: createAlertingAuthorizationAuditLoggerMock,
};

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { AlertsAuthorizationAuditLogger, ScopeType } from './audit_logger';
import { AlertingAuthorizationAuditLogger, ScopeType } from './audit_logger';
const createMockAuditLogger = () => {
return {
@ -15,46 +15,50 @@ const createMockAuditLogger = () => {
describe(`#constructor`, () => {
test('initializes a noop auditLogger if security logger is unavailable', () => {
const alertsAuditLogger = new AlertsAuthorizationAuditLogger(undefined);
const alertsAuditLogger = new AlertingAuthorizationAuditLogger(undefined);
const username = 'foo-user';
const alertTypeId = 'alert-type-id';
const scopeType = ScopeType.Consumer;
const scope = 'myApp';
const operation = 'create';
const entity = 'rule';
expect(() => {
alertsAuditLogger.alertsAuthorizationFailure(
alertsAuditLogger.logAuthorizationFailure(
username,
alertTypeId,
scopeType,
scope,
operation
operation,
entity
);
alertsAuditLogger.alertsAuthorizationSuccess(
alertsAuditLogger.logAuthorizationSuccess(
username,
alertTypeId,
scopeType,
scope,
operation
operation,
entity
);
}).not.toThrow();
});
});
describe(`#alertsUnscopedAuthorizationFailure`, () => {
describe(`#logUnscopedAuthorizationFailure`, () => {
test('logs auth failure of operation', () => {
const auditLogger = createMockAuditLogger();
const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger);
const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger);
const username = 'foo-user';
const operation = 'create';
const entity = 'rule';
alertsAuditLogger.alertsUnscopedAuthorizationFailure(username, operation);
alertsAuditLogger.logUnscopedAuthorizationFailure(username, operation, entity);
expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alerts_unscoped_authorization_failure",
"foo-user Unauthorized to create any alert types",
"alerting_unscoped_authorization_failure",
"foo-user Unauthorized to create rules for any rule types",
Object {
"operation": "create",
"username": "foo-user",
@ -65,27 +69,30 @@ describe(`#alertsUnscopedAuthorizationFailure`, () => {
test('logs auth failure with producer scope', () => {
const auditLogger = createMockAuditLogger();
const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger);
const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger);
const username = 'foo-user';
const alertTypeId = 'alert-type-id';
const scopeType = ScopeType.Producer;
const scope = 'myOtherApp';
const operation = 'create';
const entity = 'rule';
alertsAuditLogger.alertsAuthorizationFailure(
alertsAuditLogger.logAuthorizationFailure(
username,
alertTypeId,
scopeType,
scope,
operation
operation,
entity
);
expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alerts_authorization_failure",
"foo-user Unauthorized to create a \\"alert-type-id\\" alert by \\"myOtherApp\\"",
"alerting_authorization_failure",
"foo-user Unauthorized to create a \\"alert-type-id\\" rule by \\"myOtherApp\\"",
Object {
"alertTypeId": "alert-type-id",
"entity": "rule",
"operation": "create",
"scope": "myOtherApp",
"scopeType": 1,
@ -96,30 +103,33 @@ describe(`#alertsUnscopedAuthorizationFailure`, () => {
});
});
describe(`#alertsAuthorizationFailure`, () => {
describe(`#logAuthorizationFailure`, () => {
test('logs auth failure with consumer scope', () => {
const auditLogger = createMockAuditLogger();
const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger);
const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger);
const username = 'foo-user';
const alertTypeId = 'alert-type-id';
const scopeType = ScopeType.Consumer;
const scope = 'myApp';
const operation = 'create';
const entity = 'rule';
alertsAuditLogger.alertsAuthorizationFailure(
alertsAuditLogger.logAuthorizationFailure(
username,
alertTypeId,
scopeType,
scope,
operation
operation,
entity
);
expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alerts_authorization_failure",
"foo-user Unauthorized to create a \\"alert-type-id\\" alert for \\"myApp\\"",
"alerting_authorization_failure",
"foo-user Unauthorized to create a \\"alert-type-id\\" rule for \\"myApp\\"",
Object {
"alertTypeId": "alert-type-id",
"entity": "rule",
"operation": "create",
"scope": "myApp",
"scopeType": 0,
@ -131,27 +141,30 @@ describe(`#alertsAuthorizationFailure`, () => {
test('logs auth failure with producer scope', () => {
const auditLogger = createMockAuditLogger();
const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger);
const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger);
const username = 'foo-user';
const alertTypeId = 'alert-type-id';
const scopeType = ScopeType.Producer;
const scope = 'myOtherApp';
const operation = 'create';
const entity = 'rule';
alertsAuditLogger.alertsAuthorizationFailure(
alertsAuditLogger.logAuthorizationFailure(
username,
alertTypeId,
scopeType,
scope,
operation
operation,
entity
);
expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alerts_authorization_failure",
"foo-user Unauthorized to create a \\"alert-type-id\\" alert by \\"myOtherApp\\"",
"alerting_authorization_failure",
"foo-user Unauthorized to create a \\"alert-type-id\\" rule by \\"myOtherApp\\"",
Object {
"alertTypeId": "alert-type-id",
"entity": "rule",
"operation": "create",
"scope": "myOtherApp",
"scopeType": 1,
@ -162,10 +175,10 @@ describe(`#alertsAuthorizationFailure`, () => {
});
});
describe(`#alertsBulkAuthorizationSuccess`, () => {
describe(`#logBulkAuthorizationSuccess`, () => {
test('logs auth success with consumer scope', () => {
const auditLogger = createMockAuditLogger();
const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger);
const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger);
const username = 'foo-user';
const scopeType = ScopeType.Consumer;
const authorizedEntries: Array<[string, string]> = [
@ -173,18 +186,20 @@ describe(`#alertsBulkAuthorizationSuccess`, () => {
['other-alert-type-id', 'myOtherApp'],
];
const operation = 'create';
const entity = 'rule';
alertsAuditLogger.alertsBulkAuthorizationSuccess(
alertsAuditLogger.logBulkAuthorizationSuccess(
username,
authorizedEntries,
scopeType,
operation
operation,
entity
);
expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alerts_authorization_success",
"foo-user Authorized to create: \\"alert-type-id\\" alert for \\"myApp\\", \\"other-alert-type-id\\" alert for \\"myOtherApp\\"",
"alerting_authorization_success",
"foo-user Authorized to create: \\"alert-type-id\\" rules for \\"myApp\\", \\"other-alert-type-id\\" rules for \\"myOtherApp\\"",
Object {
"authorizedEntries": Array [
Array [
@ -196,6 +211,7 @@ describe(`#alertsBulkAuthorizationSuccess`, () => {
"myOtherApp",
],
],
"entity": "rule",
"operation": "create",
"scopeType": 0,
"username": "foo-user",
@ -206,7 +222,7 @@ describe(`#alertsBulkAuthorizationSuccess`, () => {
test('logs auth success with producer scope', () => {
const auditLogger = createMockAuditLogger();
const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger);
const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger);
const username = 'foo-user';
const scopeType = ScopeType.Producer;
const authorizedEntries: Array<[string, string]> = [
@ -214,18 +230,20 @@ describe(`#alertsBulkAuthorizationSuccess`, () => {
['other-alert-type-id', 'myOtherApp'],
];
const operation = 'create';
const entity = 'rule';
alertsAuditLogger.alertsBulkAuthorizationSuccess(
alertsAuditLogger.logBulkAuthorizationSuccess(
username,
authorizedEntries,
scopeType,
operation
operation,
entity
);
expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alerts_authorization_success",
"foo-user Authorized to create: \\"alert-type-id\\" alert by \\"myApp\\", \\"other-alert-type-id\\" alert by \\"myOtherApp\\"",
"alerting_authorization_success",
"foo-user Authorized to create: \\"alert-type-id\\" rules by \\"myApp\\", \\"other-alert-type-id\\" rules by \\"myOtherApp\\"",
Object {
"authorizedEntries": Array [
Array [
@ -237,6 +255,7 @@ describe(`#alertsBulkAuthorizationSuccess`, () => {
"myOtherApp",
],
],
"entity": "rule",
"operation": "create",
"scopeType": 1,
"username": "foo-user",
@ -249,27 +268,30 @@ describe(`#alertsBulkAuthorizationSuccess`, () => {
describe(`#savedObjectsAuthorizationSuccess`, () => {
test('logs auth success with consumer scope', () => {
const auditLogger = createMockAuditLogger();
const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger);
const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger);
const username = 'foo-user';
const alertTypeId = 'alert-type-id';
const scopeType = ScopeType.Consumer;
const scope = 'myApp';
const operation = 'create';
const entity = 'rule';
alertsAuditLogger.alertsAuthorizationSuccess(
alertsAuditLogger.logAuthorizationSuccess(
username,
alertTypeId,
scopeType,
scope,
operation
operation,
entity
);
expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alerts_authorization_success",
"foo-user Authorized to create a \\"alert-type-id\\" alert for \\"myApp\\"",
"alerting_authorization_success",
"foo-user Authorized to create a \\"alert-type-id\\" rule for \\"myApp\\"",
Object {
"alertTypeId": "alert-type-id",
"entity": "rule",
"operation": "create",
"scope": "myApp",
"scopeType": 0,
@ -281,27 +303,30 @@ describe(`#savedObjectsAuthorizationSuccess`, () => {
test('logs auth success with producer scope', () => {
const auditLogger = createMockAuditLogger();
const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger);
const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger);
const username = 'foo-user';
const alertTypeId = 'alert-type-id';
const scopeType = ScopeType.Producer;
const scope = 'myOtherApp';
const operation = 'create';
const entity = 'rule';
alertsAuditLogger.alertsAuthorizationSuccess(
alertsAuditLogger.logAuthorizationSuccess(
username,
alertTypeId,
scopeType,
scope,
operation
operation,
entity
);
expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alerts_authorization_success",
"foo-user Authorized to create a \\"alert-type-id\\" alert by \\"myOtherApp\\"",
"alerting_authorization_success",
"foo-user Authorized to create a \\"alert-type-id\\" rule by \\"myOtherApp\\"",
Object {
"alertTypeId": "alert-type-id",
"entity": "rule",
"operation": "create",
"scope": "myOtherApp",
"scopeType": 1,

View file

@ -17,7 +17,7 @@ export enum AuthorizationResult {
Authorized = 'Authorized',
}
export class AlertsAuthorizationAuditLogger {
export class AlertingAuthorizationAuditLogger {
private readonly auditLogger: LegacyAuditLogger;
constructor(auditLogger: LegacyAuditLogger = { log() {} }) {
@ -29,89 +29,102 @@ export class AlertsAuthorizationAuditLogger {
alertTypeId: string,
scopeType: ScopeType,
scope: string,
operation: string
operation: string,
entity: string
): string {
return `${authorizationResult} to ${operation} a "${alertTypeId}" alert ${
return `${authorizationResult} to ${operation} a "${alertTypeId}" ${entity} ${
scopeType === ScopeType.Consumer ? `for "${scope}"` : `by "${scope}"`
}`;
}
public alertsAuthorizationFailure(
public logAuthorizationFailure(
username: string,
alertTypeId: string,
scopeType: ScopeType,
scope: string,
operation: string
operation: string,
entity: string
): string {
const message = this.getAuthorizationMessage(
AuthorizationResult.Unauthorized,
alertTypeId,
scopeType,
scope,
operation
operation,
entity
);
this.auditLogger.log('alerts_authorization_failure', `${username} ${message}`, {
this.auditLogger.log('alerting_authorization_failure', `${username} ${message}`, {
username,
alertTypeId,
scopeType,
scope,
operation,
entity,
});
return message;
}
public alertsUnscopedAuthorizationFailure(username: string, operation: string): string {
const message = `Unauthorized to ${operation} any alert types`;
this.auditLogger.log('alerts_unscoped_authorization_failure', `${username} ${message}`, {
public logUnscopedAuthorizationFailure(
username: string,
operation: string,
entity: string
): string {
const message = `Unauthorized to ${operation} ${entity}s for any rule types`;
this.auditLogger.log('alerting_unscoped_authorization_failure', `${username} ${message}`, {
username,
operation,
});
return message;
}
public alertsAuthorizationSuccess(
public logAuthorizationSuccess(
username: string,
alertTypeId: string,
scopeType: ScopeType,
scope: string,
operation: string
operation: string,
entity: string
): string {
const message = this.getAuthorizationMessage(
AuthorizationResult.Authorized,
alertTypeId,
scopeType,
scope,
operation
operation,
entity
);
this.auditLogger.log('alerts_authorization_success', `${username} ${message}`, {
this.auditLogger.log('alerting_authorization_success', `${username} ${message}`, {
username,
alertTypeId,
scopeType,
scope,
operation,
entity,
});
return message;
}
public alertsBulkAuthorizationSuccess(
public logBulkAuthorizationSuccess(
username: string,
authorizedEntries: Array<[string, string]>,
scopeType: ScopeType,
operation: string
operation: string,
entity: string
): string {
const message = `${AuthorizationResult.Authorized} to ${operation}: ${authorizedEntries
.map(
([alertTypeId, scope]) =>
`"${alertTypeId}" alert ${
`"${alertTypeId}" ${entity}s ${
scopeType === ScopeType.Consumer ? `for "${scope}"` : `by "${scope}"`
}`
)
.join(', ')}`;
this.auditLogger.log('alerts_authorization_success', `${username} ${message}`, {
this.auditLogger.log('alerting_authorization_success', `${username} ${message}`, {
username,
scopeType,
authorizedEntries,
operation,
entity,
});
return message;
}

View file

@ -5,5 +5,5 @@
* 2.0.
*/
export * from './alerts_authorization';
export * from './alerts_authorization_kuery';
export * from './alerting_authorization';
export * from './alerting_authorization_kuery';

View file

@ -26,6 +26,7 @@ const createSetupMock = () => {
const createStartMock = () => {
const mock: jest.Mocked<PluginStartContract> = {
listTypes: jest.fn(),
getAlertingAuthorizationWithRequest: jest.fn(),
getAlertsClientWithRequest: jest.fn().mockResolvedValue(alertsClientMock.create()),
getFrameworkHealth: jest.fn(),
};

View file

@ -201,6 +201,58 @@ describe('Alerting Plugin', () => {
startContract.getAlertsClientWithRequest(fakeRequest);
});
});
test(`exposes getAlertingAuthorizationWithRequest()`, async () => {
const context = coreMock.createPluginInitializerContext<AlertsConfig>({
healthCheck: {
interval: '5m',
},
invalidateApiKeysTask: {
interval: '5m',
removalDelay: '1h',
},
});
const plugin = new AlertingPlugin(context);
const encryptedSavedObjectsSetup = {
...encryptedSavedObjectsMock.createSetup(),
canEncrypt: true,
};
plugin.setup(coreMock.createSetup(), {
licensing: licensingMock.createSetup(),
encryptedSavedObjects: encryptedSavedObjectsSetup,
taskManager: taskManagerMock.createSetup(),
eventLog: eventLogServiceMock.create(),
actions: actionsMock.createSetup(),
statusService: statusServiceMock.createSetupContract(),
});
const startContract = plugin.start(coreMock.createStart(), {
actions: actionsMock.createStart(),
encryptedSavedObjects: encryptedSavedObjectsMock.createStart(),
features: mockFeatures(),
licensing: licensingMock.createStart(),
eventLog: eventLogMock.createStart(),
taskManager: taskManagerMock.createStart(),
});
const fakeRequest = ({
headers: {},
getBasePath: () => '',
path: '/',
route: { settings: {} },
url: {
href: '/',
},
raw: {
req: {
url: '/',
},
},
getSavedObjectsClient: jest.fn(),
} as unknown) as KibanaRequest;
startContract.getAlertingAuthorizationWithRequest(fakeRequest);
});
});
});

View file

@ -36,7 +36,6 @@ import {
SavedObjectsBulkGetObject,
} from '../../../../src/core/server';
import type { AlertingRequestHandlerContext } from './types';
import { defineRoutes } from './routes';
import { LICENSE_TYPE, LicensingPluginSetup, LicensingPluginStart } from '../../licensing/server';
import {
@ -68,6 +67,8 @@ import {
} from './health';
import { AlertsConfig } from './config';
import { getHealth } from './health/get_health';
import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory';
import { AlertingAuthorization } from './authorization';
export const EVENT_LOG_PROVIDER = 'alerting';
export const EVENT_LOG_ACTIONS = {
@ -104,6 +105,9 @@ export interface PluginSetupContract {
export interface PluginStartContract {
listTypes: AlertTypeRegistry['list'];
getAlertsClientWithRequest(request: KibanaRequest): PublicMethodsOf<AlertsClient>;
getAlertingAuthorizationWithRequest(
request: KibanaRequest
): PublicMethodsOf<AlertingAuthorization>;
getFrameworkHealth: () => Promise<AlertsHealth>;
}
@ -137,6 +141,7 @@ export class AlertingPlugin {
private isESOCanEncrypt?: boolean;
private security?: SecurityPluginSetup;
private readonly alertsClientFactory: AlertsClientFactory;
private readonly alertingAuthorizationClientFactory: AlertingAuthorizationClientFactory;
private readonly telemetryLogger: Logger;
private readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version'];
private eventLogService?: IEventLogService;
@ -149,6 +154,7 @@ export class AlertingPlugin {
this.logger = initializerContext.logger.get('plugins', 'alerting');
this.taskRunnerFactory = new TaskRunnerFactory();
this.alertsClientFactory = new AlertsClientFactory();
this.alertingAuthorizationClientFactory = new AlertingAuthorizationClientFactory();
this.telemetryLogger = initializerContext.logger.get('usage');
this.kibanaIndexConfig = initializerContext.config.legacy.globalConfig$;
this.kibanaVersion = initializerContext.env.packageInfo.version;
@ -288,6 +294,7 @@ export class AlertingPlugin {
taskRunnerFactory,
alertTypeRegistry,
alertsClientFactory,
alertingAuthorizationClientFactory,
security,
licenseState,
} = this;
@ -304,6 +311,16 @@ export class AlertingPlugin {
: undefined;
};
alertingAuthorizationClientFactory.initialize({
alertTypeRegistry: alertTypeRegistry!,
securityPluginSetup: security,
securityPluginStart: plugins.security,
async getSpace(request: KibanaRequest) {
return plugins.spaces?.spacesService.getActiveSpace(request);
},
features: plugins.features,
});
alertsClientFactory.initialize({
alertTypeRegistry: alertTypeRegistry!,
logger,
@ -315,13 +332,10 @@ export class AlertingPlugin {
getSpaceId(request: KibanaRequest) {
return plugins.spaces?.spacesService.getSpaceId(request);
},
async getSpace(request: KibanaRequest) {
return plugins.spaces?.spacesService.getActiveSpace(request);
},
actions: plugins.actions,
features: plugins.features,
eventLog: plugins.eventLog,
kibanaVersion: this.kibanaVersion,
authorization: alertingAuthorizationClientFactory,
});
const getAlertsClientWithRequest = (request: KibanaRequest) => {
@ -333,6 +347,10 @@ export class AlertingPlugin {
return alertsClientFactory!.create(request, core.savedObjects);
};
const getAlertingAuthorizationWithRequest = (request: KibanaRequest) => {
return alertingAuthorizationClientFactory!.create(request);
};
taskRunnerFactory.initialize({
logger,
getServices: this.getServicesFactory(core.savedObjects, core.elasticsearch),
@ -362,6 +380,7 @@ export class AlertingPlugin {
return {
listTypes: alertTypeRegistry!.list.bind(this.alertTypeRegistry!),
getAlertingAuthorizationWithRequest,
getAlertsClientWithRequest,
getFrameworkHealth: async () =>
await getHealth(core.savedObjects.createInternalRepository(['alert'])),

View file

@ -1,16 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`#get alertType of "" throws error 1`] = `"alertTypeId is required and must be a string"`;
exports[`#get alertingType of "" throws error 1`] = `"alertingEntity is required and must be a string"`;
exports[`#get alertType of {} throws error 1`] = `"alertTypeId is required and must be a string"`;
exports[`#get alertingType of {} throws error 1`] = `"alertingEntity is required and must be a string"`;
exports[`#get alertType of 1 throws error 1`] = `"alertTypeId is required and must be a string"`;
exports[`#get alertingType of 1 throws error 1`] = `"alertingEntity is required and must be a string"`;
exports[`#get alertType of null throws error 1`] = `"alertTypeId is required and must be a string"`;
exports[`#get alertingType of null throws error 1`] = `"alertingEntity is required and must be a string"`;
exports[`#get alertType of true throws error 1`] = `"alertTypeId is required and must be a string"`;
exports[`#get alertingType of true throws error 1`] = `"alertingEntity is required and must be a string"`;
exports[`#get alertType of undefined throws error 1`] = `"alertTypeId is required and must be a string"`;
exports[`#get alertingType of undefined throws error 1`] = `"alertingEntity is required and must be a string"`;
exports[`#get consumer of "" throws error 1`] = `"consumer is required and must be a string"`;
@ -35,3 +35,15 @@ exports[`#get operation of null throws error 1`] = `"operation is required and m
exports[`#get operation of true throws error 1`] = `"operation is required and must be a string"`;
exports[`#get operation of undefined throws error 1`] = `"operation is required and must be a string"`;
exports[`#get ruleType of "" throws error 1`] = `"ruleTypeId is required and must be a string"`;
exports[`#get ruleType of {} throws error 1`] = `"ruleTypeId is required and must be a string"`;
exports[`#get ruleType of 1 throws error 1`] = `"ruleTypeId is required and must be a string"`;
exports[`#get ruleType of null throws error 1`] = `"ruleTypeId is required and must be a string"`;
exports[`#get ruleType of true throws error 1`] = `"ruleTypeId is required and must be a string"`;
exports[`#get ruleType of undefined throws error 1`] = `"ruleTypeId is required and must be a string"`;

View file

@ -10,11 +10,11 @@ import { AlertingActions } from './alerting';
const version = '1.0.0-zeta1';
describe('#get', () => {
[null, undefined, '', 1, true, {}].forEach((alertType: any) => {
test(`alertType of ${JSON.stringify(alertType)} throws error`, () => {
[null, undefined, '', 1, true, {}].forEach((ruleType: any) => {
test(`ruleType of ${JSON.stringify(ruleType)} throws error`, () => {
const alertingActions = new AlertingActions(version);
expect(() =>
alertingActions.get(alertType, 'consumer', 'foo-action')
alertingActions.get(ruleType, 'consumer', 'alertingType', 'foo-action')
).toThrowErrorMatchingSnapshot();
});
});
@ -23,7 +23,7 @@ describe('#get', () => {
test(`operation of ${JSON.stringify(operation)} throws error`, () => {
const alertingActions = new AlertingActions(version);
expect(() =>
alertingActions.get('foo-alertType', 'consumer', operation)
alertingActions.get('foo-ruleType', 'consumer', 'alertingType', operation)
).toThrowErrorMatchingSnapshot();
});
});
@ -32,15 +32,24 @@ describe('#get', () => {
test(`consumer of ${JSON.stringify(consumer)} throws error`, () => {
const alertingActions = new AlertingActions(version);
expect(() =>
alertingActions.get('foo-alertType', consumer, 'operation')
alertingActions.get('foo-ruleType', consumer, 'alertingType', 'operation')
).toThrowErrorMatchingSnapshot();
});
});
test('returns `alerting:${alertType}/${consumer}/${operation}`', () => {
[null, '', 1, true, undefined, {}].forEach((alertingType: any) => {
test(`alertingType of ${JSON.stringify(alertingType)} throws error`, () => {
const alertingActions = new AlertingActions(version);
expect(() =>
alertingActions.get('foo-ruleType', 'consumer', alertingType, 'operation')
).toThrowErrorMatchingSnapshot();
});
});
test('returns `alerting:${ruleType}/${consumer}/${alertingType}/${operation}`', () => {
const alertingActions = new AlertingActions(version);
expect(alertingActions.get('foo-alertType', 'consumer', 'bar-operation')).toBe(
'alerting:1.0.0-zeta1:foo-alertType/consumer/bar-operation'
expect(alertingActions.get('foo-ruleType', 'consumer', 'alertingType', 'bar-operation')).toBe(
'alerting:1.0.0-zeta1:foo-ruleType/consumer/alertingType/bar-operation'
);
});
});

View file

@ -14,9 +14,14 @@ export class AlertingActions {
this.prefix = `alerting:${versionNumber}:`;
}
public get(alertTypeId: string, consumer: string, operation: string): string {
if (!alertTypeId || !isString(alertTypeId)) {
throw new Error('alertTypeId is required and must be a string');
public get(
ruleTypeId: string,
consumer: string,
alertingEntity: string,
operation: string
): string {
if (!ruleTypeId || !isString(ruleTypeId)) {
throw new Error('ruleTypeId is required and must be a string');
}
if (!operation || !isString(operation)) {
@ -27,6 +32,10 @@ export class AlertingActions {
throw new Error('consumer is required and must be a string');
}
return `${this.prefix}${alertTypeId}/${consumer}/${operation}`;
if (!alertingEntity || !isString(alertingEntity)) {
throw new Error('alertingEntity is required and must be a string');
}
return `${this.prefix}${ruleTypeId}/${consumer}/${alertingEntity}/${operation}`;
}
}

View file

@ -76,10 +76,12 @@ describe(`feature_privilege_builder`, () => {
expect(alertingFeaturePrivileges.getActions(privilege, feature)).toMatchInlineSnapshot(`
Array [
"alerting:1.0.0-zeta1:alert-type/my-feature/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/getAlertState",
"alerting:1.0.0-zeta1:alert-type/my-feature/getAlertInstanceSummary",
"alerting:1.0.0-zeta1:alert-type/my-feature/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleState",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/find",
]
`);
});
@ -114,20 +116,23 @@ describe(`feature_privilege_builder`, () => {
expect(alertingFeaturePrivileges.getActions(privilege, feature)).toMatchInlineSnapshot(`
Array [
"alerting:1.0.0-zeta1:alert-type/my-feature/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/getAlertState",
"alerting:1.0.0-zeta1:alert-type/my-feature/getAlertInstanceSummary",
"alerting:1.0.0-zeta1:alert-type/my-feature/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/create",
"alerting:1.0.0-zeta1:alert-type/my-feature/delete",
"alerting:1.0.0-zeta1:alert-type/my-feature/update",
"alerting:1.0.0-zeta1:alert-type/my-feature/updateApiKey",
"alerting:1.0.0-zeta1:alert-type/my-feature/enable",
"alerting:1.0.0-zeta1:alert-type/my-feature/disable",
"alerting:1.0.0-zeta1:alert-type/my-feature/muteAll",
"alerting:1.0.0-zeta1:alert-type/my-feature/unmuteAll",
"alerting:1.0.0-zeta1:alert-type/my-feature/muteInstance",
"alerting:1.0.0-zeta1:alert-type/my-feature/unmuteInstance",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleState",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/create",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/delete",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/update",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/updateApiKey",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/enable",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/disable",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/muteAll",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAll",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/muteAlert",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/update",
]
`);
});
@ -162,24 +167,29 @@ describe(`feature_privilege_builder`, () => {
expect(alertingFeaturePrivileges.getActions(privilege, feature)).toMatchInlineSnapshot(`
Array [
"alerting:1.0.0-zeta1:alert-type/my-feature/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/getAlertState",
"alerting:1.0.0-zeta1:alert-type/my-feature/getAlertInstanceSummary",
"alerting:1.0.0-zeta1:alert-type/my-feature/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/create",
"alerting:1.0.0-zeta1:alert-type/my-feature/delete",
"alerting:1.0.0-zeta1:alert-type/my-feature/update",
"alerting:1.0.0-zeta1:alert-type/my-feature/updateApiKey",
"alerting:1.0.0-zeta1:alert-type/my-feature/enable",
"alerting:1.0.0-zeta1:alert-type/my-feature/disable",
"alerting:1.0.0-zeta1:alert-type/my-feature/muteAll",
"alerting:1.0.0-zeta1:alert-type/my-feature/unmuteAll",
"alerting:1.0.0-zeta1:alert-type/my-feature/muteInstance",
"alerting:1.0.0-zeta1:alert-type/my-feature/unmuteInstance",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/get",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/getAlertState",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/getAlertInstanceSummary",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleState",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/create",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/delete",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/update",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/updateApiKey",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/enable",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/disable",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/muteAll",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAll",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/muteAlert",
"alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/update",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/get",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getRuleState",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getAlertSummary",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/find",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/find",
]
`);
});

View file

@ -10,20 +10,35 @@ import { uniq } from 'lodash';
import type { FeatureKibanaPrivileges, KibanaFeature } from '../../../../../features/server';
import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder';
const readOperations: string[] = ['get', 'getAlertState', 'getAlertInstanceSummary', 'find'];
const writeOperations: string[] = [
'create',
'delete',
'update',
'updateApiKey',
'enable',
'disable',
'muteAll',
'unmuteAll',
'muteInstance',
'unmuteInstance',
];
const allOperations: string[] = [...readOperations, ...writeOperations];
enum AlertingType {
RULE = 'rule',
ALERT = 'alert',
}
const readOperations: Record<AlertingType, string[]> = {
rule: ['get', 'getRuleState', 'getAlertSummary', 'find'],
alert: ['get', 'find'],
};
const writeOperations: Record<AlertingType, string[]> = {
rule: [
'create',
'delete',
'update',
'updateApiKey',
'enable',
'disable',
'muteAll',
'unmuteAll',
'muteAlert',
'unmuteAlert',
],
alert: ['update'],
};
const allOperations: Record<AlertingType, string[]> = {
rule: [...readOperations.rule, ...writeOperations.rule],
alert: [...readOperations.alert, ...writeOperations.alert],
};
export class FeaturePrivilegeAlertingBuilder extends BaseFeaturePrivilegeBuilder {
public getActions(
@ -31,12 +46,16 @@ export class FeaturePrivilegeAlertingBuilder extends BaseFeaturePrivilegeBuilder
feature: KibanaFeature
): string[] {
const getAlertingPrivilege = (
operations: string[],
operations: Record<AlertingType, string[]>,
privilegedTypes: readonly string[],
consumer: string
) =>
privilegedTypes.flatMap((type) =>
operations.map((operation) => this.actions.alerting.get(type, consumer, operation))
privilegedTypes.flatMap((privilegedType) =>
Object.values(AlertingType).flatMap((alertingType) =>
operations[alertingType].map((operation) =>
this.actions.alerting.get(privilegedType, consumer, alertingType, operation)
)
)
);
return uniq([

View file

@ -308,7 +308,7 @@ export function getConsumerUnauthorizedErrorMessage(
alertType: string,
consumer: string
) {
return `Unauthorized to ${operation} a "${alertType}" alert for "${consumer}"`;
return `Unauthorized to ${operation} a "${alertType}" rule for "${consumer}"`;
}
export function getProducerUnauthorizedErrorMessage(
@ -316,7 +316,7 @@ export function getProducerUnauthorizedErrorMessage(
alertType: string,
producer: string
) {
return `Unauthorized to ${operation} a "${alertType}" alert by "${producer}"`;
return `Unauthorized to ${operation} a "${alertType}" rule by "${producer}"`;
}
function getDefaultAlwaysFiringAlertData(reference: string, actionId: string) {

View file

@ -47,7 +47,7 @@ export default function createFindTests({ getService }: FtrProviderContext) {
expect(response.statusCode).to.eql(403);
expect(response.body).to.eql({
error: 'Forbidden',
message: `Unauthorized to find any alert types`,
message: `Unauthorized to find rules for any rule types`,
statusCode: 403,
});
break;
@ -143,7 +143,7 @@ export default function createFindTests({ getService }: FtrProviderContext) {
expect(response.statusCode).to.eql(403);
expect(response.body).to.eql({
error: 'Forbidden',
message: `Unauthorized to find any alert types`,
message: `Unauthorized to find rules for any rule types`,
statusCode: 403,
});
break;
@ -239,7 +239,7 @@ export default function createFindTests({ getService }: FtrProviderContext) {
expect(response.statusCode).to.eql(403);
expect(response.body).to.eql({
error: 'Forbidden',
message: `Unauthorized to find any alert types`,
message: `Unauthorized to find rules for any rule types`,
statusCode: 403,
});
break;
@ -333,7 +333,7 @@ export default function createFindTests({ getService }: FtrProviderContext) {
expect(response.statusCode).to.eql(403);
expect(response.body).to.eql({
error: 'Forbidden',
message: `Unauthorized to find any alert types`,
message: `Unauthorized to find rules for any rule types`,
statusCode: 403,
});
break;
@ -410,7 +410,7 @@ export default function createFindTests({ getService }: FtrProviderContext) {
expect(response.statusCode).to.eql(403);
expect(response.body).to.eql({
error: 'Forbidden',
message: `Unauthorized to find any alert types`,
message: `Unauthorized to find rules for any rule types`,
statusCode: 403,
});
break;
@ -470,7 +470,7 @@ export default function createFindTests({ getService }: FtrProviderContext) {
expect(response.statusCode).to.eql(403);
expect(response.body).to.eql({
error: 'Forbidden',
message: `Unauthorized to find any alert types`,
message: `Unauthorized to find rules for any rule types`,
statusCode: 403,
});
break;

View file

@ -73,7 +73,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getConsumerUnauthorizedErrorMessage(
'muteInstance',
'muteAlert',
'test.noop',
'alertsFixture'
),
@ -138,7 +138,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getConsumerUnauthorizedErrorMessage(
'muteInstance',
'muteAlert',
'test.restricted-noop',
'alertsRestrictedFixture'
),
@ -192,7 +192,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getConsumerUnauthorizedErrorMessage(
'muteInstance',
'muteAlert',
'test.unrestricted-noop',
'alertsFixture'
),
@ -205,7 +205,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getProducerUnauthorizedErrorMessage(
'muteInstance',
'muteAlert',
'test.unrestricted-noop',
'alertsRestrictedFixture'
),
@ -258,7 +258,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getConsumerUnauthorizedErrorMessage(
'muteInstance',
'muteAlert',
'test.restricted-noop',
'alerts'
),
@ -272,7 +272,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getProducerUnauthorizedErrorMessage(
'muteInstance',
'muteAlert',
'test.restricted-noop',
'alertsRestrictedFixture'
),
@ -325,7 +325,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getConsumerUnauthorizedErrorMessage(
'muteInstance',
'muteAlert',
'test.noop',
'alertsFixture'
),

View file

@ -112,7 +112,7 @@ export default function alertTests({ getService }: FtrProviderContext) {
expect(failedUpdateKeyDueToAlertsPrivilegesResponse.body).to.eql({
error: 'Forbidden',
message:
'Unauthorized to updateApiKey a "test.always-firing" alert for "alertsFixture"',
'Unauthorized to updateApiKey a "test.always-firing" rule for "alertsFixture"',
statusCode: 403,
});
break;

View file

@ -78,7 +78,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getConsumerUnauthorizedErrorMessage(
'unmuteInstance',
'unmuteAlert',
'test.noop',
'alertsFixture'
),
@ -148,7 +148,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getConsumerUnauthorizedErrorMessage(
'unmuteInstance',
'unmuteAlert',
'test.restricted-noop',
'alertsRestrictedFixture'
),
@ -207,7 +207,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getConsumerUnauthorizedErrorMessage(
'unmuteInstance',
'unmuteAlert',
'test.unrestricted-noop',
'alertsFixture'
),
@ -220,7 +220,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getProducerUnauthorizedErrorMessage(
'unmuteInstance',
'unmuteAlert',
'test.unrestricted-noop',
'alertsRestrictedFixture'
),
@ -278,7 +278,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getConsumerUnauthorizedErrorMessage(
'unmuteInstance',
'unmuteAlert',
'test.restricted-noop',
'alerts'
),
@ -292,7 +292,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider
expect(response.body).to.eql({
error: 'Forbidden',
message: getProducerUnauthorizedErrorMessage(
'unmuteInstance',
'unmuteAlert',
'test.restricted-noop',
'alertsRestrictedFixture'
),