Skip to content

Commit

Permalink
[Alerts][License] Add license checks to alerts HTTP APIs and execution (
Browse files Browse the repository at this point in the history
#85223)

* [Alerts][License] Add license checks to alerts HTTP APIs and execution

* fixed typechecks

* resolved conflicts

* resolved conflicts

* added router tests

* fixed typechecks

* added license check support for alert task running

* fixed typechecks

* added integration tests

* fixed due to comments

* fixed due to comments

* fixed tests

* fixed typechecks
  • Loading branch information
YulNaumenko authored Dec 9, 2020
1 parent 09d437f commit 93614af
Show file tree
Hide file tree
Showing 67 changed files with 1,129 additions and 183 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/actions/server/action_type_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export class ActionTypeRegistry {
minimumLicenseRequired: actionType.minimumLicenseRequired,
enabled: this.isActionTypeEnabled(actionTypeId),
enabledInConfig: this.actionsConfigUtils.isActionTypeEnabled(actionTypeId),
enabledInLicense: this.licenseState.isLicenseValidForActionType(actionType).isValid === true,
enabledInLicense: !!this.licenseState.isLicenseValidForActionType(actionType).isValid,
}));
}
}
1 change: 1 addition & 0 deletions x-pack/plugins/alerts/common/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export enum AlertExecutionStatusErrorReasons {
Decrypt = 'decrypt',
Execute = 'execute',
Unknown = 'unknown',
License = 'license',
}

export interface AlertExecutionStatus {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/alerts/server/alert_type_registry.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const createAlertTypeRegistryMock = () => {
register: jest.fn(),
get: jest.fn(),
list: jest.fn(),
ensureAlertTypeEnabled: jest.fn(),
};
return mocked;
};
Expand Down
60 changes: 54 additions & 6 deletions x-pack/plugins/alerts/server/alert_type_registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,27 @@
*/

import { TaskRunnerFactory } from './task_runner';
import { AlertTypeRegistry } from './alert_type_registry';
import { AlertTypeRegistry, ConstructorOptions } from './alert_type_registry';
import { AlertType } from './types';
import { taskManagerMock } from '../../task_manager/server/mocks';
import { ILicenseState } from './lib/license_state';
import { licenseStateMock } from './lib/license_state.mock';
import { licensingMock } from '../../licensing/server/mocks';
let mockedLicenseState: jest.Mocked<ILicenseState>;
let alertTypeRegistryParams: ConstructorOptions;

const taskManager = taskManagerMock.createSetup();
const alertTypeRegistryParams = {
taskManager,
taskRunnerFactory: new TaskRunnerFactory(),
};

beforeEach(() => jest.resetAllMocks());
beforeEach(() => {
jest.resetAllMocks();
mockedLicenseState = licenseStateMock.create();
alertTypeRegistryParams = {
taskManager,
taskRunnerFactory: new TaskRunnerFactory(),
licenseState: mockedLicenseState,
licensing: licensingMock.createSetup(),
};
});

describe('has()', () => {
test('returns false for unregistered alert types', () => {
Expand Down Expand Up @@ -379,6 +389,7 @@ describe('list()', () => {
"state": Array [],
},
"defaultActionGroupId": "testActionGroup",
"enabledInLicense": false,
"id": "test",
"minimumLicenseRequired": "basic",
"name": "Test",
Expand Down Expand Up @@ -427,6 +438,43 @@ describe('list()', () => {
});
});

describe('ensureAlertTypeEnabled', () => {
let alertTypeRegistry: AlertTypeRegistry;

beforeEach(() => {
alertTypeRegistry = new AlertTypeRegistry(alertTypeRegistryParams);
alertTypeRegistry.register({
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
executor: jest.fn(),
producer: 'alerts',
minimumLicenseRequired: 'basic',
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
});
});

test('should call ensureLicenseForAlertType on the license state', async () => {
alertTypeRegistry.ensureAlertTypeEnabled('test');
expect(mockedLicenseState.ensureLicenseForAlertType).toHaveBeenCalled();
});

test('should throw when ensureLicenseForAlertType throws', async () => {
mockedLicenseState.ensureLicenseForAlertType.mockImplementation(() => {
throw new Error('Fail');
});
expect(() =>
alertTypeRegistry.ensureAlertTypeEnabled('test')
).toThrowErrorMatchingInlineSnapshot(`"Fail"`);
});
});

function alertTypeWithVariables(id: string, context: string, state: string): AlertType {
const baseAlert: AlertType = {
id,
Expand Down
31 changes: 29 additions & 2 deletions x-pack/plugins/alerts/server/alert_type_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
import { schema } from '@kbn/config-schema';
import typeDetect from 'type-detect';
import { intersection } from 'lodash';
import { LicensingPluginSetup } from '../../licensing/server';
import { RunContext, TaskManagerSetupContract } from '../../task_manager/server';
import { TaskRunnerFactory } from './task_runner';
import {
Expand All @@ -19,10 +20,14 @@ import {
AlertInstanceContext,
} from './types';
import { RecoveredActionGroup, getBuiltinActionGroups } from '../common';
import { ILicenseState } from './lib/license_state';
import { getAlertTypeFeatureUsageName } from './lib/get_alert_type_feature_usage_name';

interface ConstructorOptions {
export interface ConstructorOptions {
taskManager: TaskManagerSetupContract;
taskRunnerFactory: TaskRunnerFactory;
licenseState: ILicenseState;
licensing: LicensingPluginSetup;
}

export interface RegistryAlertType
Expand All @@ -34,8 +39,10 @@ export interface RegistryAlertType
| 'defaultActionGroupId'
| 'actionVariables'
| 'producer'
| 'minimumLicenseRequired'
> {
id: string;
enabledInLicense: boolean;
}

/**
Expand Down Expand Up @@ -70,16 +77,24 @@ export class AlertTypeRegistry {
private readonly taskManager: TaskManagerSetupContract;
private readonly alertTypes: Map<string, NormalizedAlertType> = new Map();
private readonly taskRunnerFactory: TaskRunnerFactory;
private readonly licenseState: ILicenseState;
private readonly licensing: LicensingPluginSetup;

constructor({ taskManager, taskRunnerFactory }: ConstructorOptions) {
constructor({ taskManager, taskRunnerFactory, licenseState, licensing }: ConstructorOptions) {
this.taskManager = taskManager;
this.taskRunnerFactory = taskRunnerFactory;
this.licenseState = licenseState;
this.licensing = licensing;
}

public has(id: string) {
return this.alertTypes.has(id);
}

public ensureAlertTypeEnabled(id: string) {
this.licenseState.ensureLicenseForAlertType(this.get(id));
}

public register<
Params extends AlertTypeParams = AlertTypeParams,
State extends AlertTypeState = AlertTypeState,
Expand Down Expand Up @@ -108,6 +123,13 @@ export class AlertTypeRegistry {
this.taskRunnerFactory.create(normalizedAlertType, context),
},
});
// No need to notify usage on basic alert types
if (alertType.minimumLicenseRequired !== 'basic') {
this.licensing.featureUsage.register(
getAlertTypeFeatureUsageName(alertType.name),
alertType.minimumLicenseRequired
);
}
}

public get<
Expand Down Expand Up @@ -157,6 +179,11 @@ export class AlertTypeRegistry {
actionVariables,
producer,
minimumLicenseRequired,
enabledInLicense: !!this.licenseState.getLicenseCheckForAlertType(
id,
name,
minimumLicenseRequired
).isValid,
})
)
);
Expand Down
18 changes: 18 additions & 0 deletions x-pack/plugins/alerts/server/alerts_client/alerts_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ export class AlertsClient {
throw error;
}

this.alertTypeRegistry.ensureAlertTypeEnabled(data.alertTypeId);

// Throws an error if alert type isn't registered
const alertType = this.alertTypeRegistry.get(data.alertTypeId);

Expand Down Expand Up @@ -644,6 +646,8 @@ export class AlertsClient {
})
);

this.alertTypeRegistry.ensureAlertTypeEnabled(alertSavedObject.attributes.alertTypeId);

const updateResult = await this.updateAlert({ id, data }, alertSavedObject);

await Promise.all([
Expand Down Expand Up @@ -819,6 +823,8 @@ export class AlertsClient {
})
);

this.alertTypeRegistry.ensureAlertTypeEnabled(attributes.alertTypeId);

try {
await this.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version });
} catch (e) {
Expand Down Expand Up @@ -902,6 +908,8 @@ export class AlertsClient {
})
);

this.alertTypeRegistry.ensureAlertTypeEnabled(attributes.alertTypeId);

if (attributes.enabled === false) {
const username = await this.getUserName();
const updateAttributes = this.updateMeta({
Expand Down Expand Up @@ -1001,6 +1009,8 @@ export class AlertsClient {
})
);

this.alertTypeRegistry.ensureAlertTypeEnabled(attributes.alertTypeId);

if (attributes.enabled === true) {
await this.unsecuredSavedObjectsClient.update(
'alert',
Expand Down Expand Up @@ -1075,6 +1085,8 @@ export class AlertsClient {
})
);

this.alertTypeRegistry.ensureAlertTypeEnabled(attributes.alertTypeId);

const updateAttributes = this.updateMeta({
muteAll: true,
mutedInstanceIds: [],
Expand Down Expand Up @@ -1134,6 +1146,8 @@ export class AlertsClient {
})
);

this.alertTypeRegistry.ensureAlertTypeEnabled(attributes.alertTypeId);

const updateAttributes = this.updateMeta({
muteAll: false,
mutedInstanceIds: [],
Expand Down Expand Up @@ -1193,6 +1207,8 @@ export class AlertsClient {
})
);

this.alertTypeRegistry.ensureAlertTypeEnabled(attributes.alertTypeId);

const mutedInstanceIds = attributes.mutedInstanceIds || [];
if (!attributes.muteAll && !mutedInstanceIds.includes(alertInstanceId)) {
mutedInstanceIds.push(alertInstanceId);
Expand Down Expand Up @@ -1257,6 +1273,8 @@ export class AlertsClient {
})
);

this.alertTypeRegistry.ensureAlertTypeEnabled(attributes.alertTypeId);

const mutedInstanceIds = attributes.mutedInstanceIds || [];
if (!attributes.muteAll && mutedInstanceIds.includes(alertInstanceId)) {
await this.unsecuredSavedObjectsClient.update<RawAlert>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ActionsAuthorization } from '../../../../actions/server';
import { getBeforeSetup, setGlobalDate } from './lib';
import { AlertExecutionStatusValues } from '../../types';
import { RecoveredActionGroup } from '../../../common';
import { RegistryAlertType } from '../../alert_type_registry';

const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
Expand Down Expand Up @@ -49,7 +50,7 @@ beforeEach(() => {
setGlobalDate();

describe('aggregate()', () => {
const listedTypes = new Set([
const listedTypes = new Set<RegistryAlertType>([
{
actionGroups: [],
actionVariables: undefined,
Expand All @@ -59,6 +60,7 @@ describe('aggregate()', () => {
id: 'myType',
name: 'myType',
producer: 'myApp',
enabledInLicense: true,
},
]);
beforeEach(() => {
Expand Down Expand Up @@ -111,6 +113,7 @@ describe('aggregate()', () => {
authorizedConsumers: {
myApp: { read: true, all: true },
},
enabledInLicense: true,
},
])
);
Expand Down
10 changes: 10 additions & 0 deletions x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1194,4 +1194,14 @@ describe('create()', () => {
}
);
});

test('throws error when ensureActionTypeEnabled throws', async () => {
const data = getMockData();
alertTypeRegistry.ensureAlertTypeEnabled.mockImplementation(() => {
throw new Error('Fail');
});
await expect(alertsClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(
`"Fail"`
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { httpServerMock } from '../../../../../../src/core/server/mocks';
import { auditServiceMock } from '../../../../security/server/audit/index.mock';
import { getBeforeSetup, setGlobalDate } from './lib';
import { RecoveredActionGroup } from '../../../common';
import { RegistryAlertType } from '../../alert_type_registry';

const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
Expand Down Expand Up @@ -53,7 +54,7 @@ beforeEach(() => {
setGlobalDate();

describe('find()', () => {
const listedTypes = new Set([
const listedTypes = new Set<RegistryAlertType>([
{
actionGroups: [],
recoveryActionGroup: RecoveredActionGroup,
Expand All @@ -63,6 +64,7 @@ describe('find()', () => {
id: 'myType',
name: 'myType',
producer: 'myApp',
enabledInLicense: true,
},
]);
beforeEach(() => {
Expand Down Expand Up @@ -121,6 +123,7 @@ describe('find()', () => {
authorizedConsumers: {
myApp: { read: true, all: true },
},
enabledInLicense: true,
},
])
);
Expand Down
Loading

0 comments on commit 93614af

Please sign in to comment.