Skip to content

Commit

Permalink
make dedupKey required for resolve and acknowledge
Browse files Browse the repository at this point in the history
  • Loading branch information
gmmorris committed Sep 22, 2020
1 parent ce55b27 commit 528193f
Show file tree
Hide file tree
Showing 8 changed files with 392 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,16 @@ describe('validateParams()', () => {
});
}).toThrowError(`error validating action params: error parsing timestamp "${timestamp}"`);
});

test('should validate and throw error when dedupKey is missing on resolve', () => {
expect(() => {
validateParams(actionType, {
eventAction: 'resolve',
});
}).toThrowError(
`error validating action params: DedupKey is required when eventAction is "resolve"`
);
});
});

describe('execute()', () => {
Expand Down
14 changes: 13 additions & 1 deletion x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export type ActionParamsType = TypeOf<typeof ParamsSchema>;
const EVENT_ACTION_TRIGGER = 'trigger';
const EVENT_ACTION_RESOLVE = 'resolve';
const EVENT_ACTION_ACKNOWLEDGE = 'acknowledge';
const EVENT_ACTIONS_WITH_REQUIRED_DEDUPKEY = new Set([
EVENT_ACTION_RESOLVE,
EVENT_ACTION_ACKNOWLEDGE,
]);

const EventActionSchema = schema.oneOf([
schema.literal(EVENT_ACTION_TRIGGER),
Expand Down Expand Up @@ -81,7 +85,7 @@ const ParamsSchema = schema.object(
);

function validateParams(paramsObject: unknown): string | void {
const { timestamp } = paramsObject as ActionParamsType;
const { timestamp, eventAction, dedupKey } = paramsObject as ActionParamsType;
if (timestamp != null) {
try {
const date = Date.parse(timestamp);
Expand All @@ -103,6 +107,14 @@ function validateParams(paramsObject: unknown): string | void {
});
}
}
if (EVENT_ACTIONS_WITH_REQUIRED_DEDUPKEY.has(eventAction) && !dedupKey) {
return i18n.translate('xpack.actions.builtin.pagerduty.missingDedupkeyErrorMessage', {
defaultMessage: `DedupKey is required when eventAction is "{eventAction}"`,
values: {
eventAction,
},
});
}
}

// action type definition
Expand Down
114 changes: 114 additions & 0 deletions x-pack/plugins/alerts/server/saved_objects/migrations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,120 @@ describe('7.10.0', () => {
},
});
});

test('migrates PagerDuty actions to set a default dedupkey of the AlertId', () => {
const migration710 = getMigrations(encryptedSavedObjectsSetup)['7.10.0'];
const alert = getMockData({
actions: [
{
actionTypeId: '.pagerduty',
group: 'default',
params: {
summary: 'fired {{alertInstanceId}}',
eventAction: 'resolve',
component: '',
},
id: 'b62ea790-5366-4abc-a7df-33db1db78410',
},
],
});
expect(migration710(alert, { log })).toMatchObject({
...alert,
attributes: {
...alert.attributes,
actions: [
{
actionTypeId: '.pagerduty',
group: 'default',
params: {
summary: 'fired {{alertInstanceId}}',
eventAction: 'resolve',
dedupKey: '{{alertId}}',
component: '',
},
id: 'b62ea790-5366-4abc-a7df-33db1db78410',
},
],
},
});
});

test('skips PagerDuty actions with a specified dedupkey', () => {
const migration710 = getMigrations(encryptedSavedObjectsSetup)['7.10.0'];
const alert = getMockData({
actions: [
{
actionTypeId: '.pagerduty',
group: 'default',
params: {
summary: 'fired {{alertInstanceId}}',
eventAction: 'trigger',
dedupKey: '{{alertInstanceId}}',
component: '',
},
id: 'b62ea790-5366-4abc-a7df-33db1db78410',
},
],
});
expect(migration710(alert, { log })).toMatchObject({
...alert,
attributes: {
...alert.attributes,
actions: [
{
actionTypeId: '.pagerduty',
group: 'default',
params: {
summary: 'fired {{alertInstanceId}}',
eventAction: 'trigger',
dedupKey: '{{alertInstanceId}}',
component: '',
},
id: 'b62ea790-5366-4abc-a7df-33db1db78410',
},
],
},
});
});

test('skips PagerDuty actions with an eventAction of "trigger"', () => {
const migration710 = getMigrations(encryptedSavedObjectsSetup)['7.10.0'];
const alert = getMockData({
actions: [
{
actionTypeId: '.pagerduty',
group: 'default',
params: {
summary: 'fired {{alertInstanceId}}',
eventAction: 'trigger',
component: '',
},
id: 'b62ea790-5366-4abc-a7df-33db1db78410',
},
],
});
expect(migration710(alert, { log })).toEqual({
...alert,
attributes: {
...alert.attributes,
meta: {
versionApiKeyLastmodified: 'pre-7.10.0',
},
actions: [
{
actionTypeId: '.pagerduty',
group: 'default',
params: {
summary: 'fired {{alertInstanceId}}',
eventAction: 'trigger',
component: '',
},
id: 'b62ea790-5366-4abc-a7df-33db1db78410',
},
],
},
});
});
});

describe('7.10.0 migrates with failure', () => {
Expand Down
84 changes: 60 additions & 24 deletions x-pack/plugins/alerts/server/saved_objects/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,20 @@ import {

export const LEGACY_LAST_MODIFIED_VERSION = 'pre-7.10.0';

type AlertMigration = (
doc: SavedObjectUnsanitizedDoc<RawAlert>
) => SavedObjectUnsanitizedDoc<RawAlert>;

export function getMigrations(
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup
): SavedObjectMigrationMap {
const migrationWhenRBACWasIntroduced = markAsLegacyAndChangeConsumer(encryptedSavedObjects);
const migrationWhenRBACWasIntroduced = encryptedSavedObjects.createMigration<RawAlert, RawAlert>(
function shouldBeMigrated(doc): doc is SavedObjectUnsanitizedDoc<RawAlert> {
// migrate all documents in 7.10 in order to add the "meta" RBAC field
return true;
},
pipeMigrations(markAsLegacyAndChangeConsumer, setAlertIdAsDefaultDedupkeyOnPagerDutyActions)
);

return {
'7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'),
Expand Down Expand Up @@ -52,29 +62,55 @@ const consumersToChange: Map<string, string> = new Map(
[SIEM_APP_ID]: SIEM_SERVER_APP_ID,
})
);

function markAsLegacyAndChangeConsumer(
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup
): SavedObjectMigrationFn<RawAlert, RawAlert> {
return encryptedSavedObjects.createMigration<RawAlert, RawAlert>(
function shouldBeMigrated(doc): doc is SavedObjectUnsanitizedDoc<RawAlert> {
// migrate all documents in 7.10 in order to add the "meta" RBAC field
return true;
doc: SavedObjectUnsanitizedDoc<RawAlert>
): SavedObjectUnsanitizedDoc<RawAlert> {
const {
attributes: { consumer },
} = doc;
return {
...doc,
attributes: {
...doc.attributes,
consumer: consumersToChange.get(consumer) ?? consumer,
// mark any alert predating 7.10 as a legacy alert
meta: {
versionApiKeyLastmodified: LEGACY_LAST_MODIFIED_VERSION,
},
},
(doc: SavedObjectUnsanitizedDoc<RawAlert>): SavedObjectUnsanitizedDoc<RawAlert> => {
const {
attributes: { consumer },
} = doc;
return {
...doc,
attributes: {
...doc.attributes,
consumer: consumersToChange.get(consumer) ?? consumer,
// mark any alert predating 7.10 as a legacy alert
meta: {
versionApiKeyLastmodified: LEGACY_LAST_MODIFIED_VERSION,
},
},
};
}
);
};
}

function setAlertIdAsDefaultDedupkeyOnPagerDutyActions(
doc: SavedObjectUnsanitizedDoc<RawAlert>
): SavedObjectUnsanitizedDoc<RawAlert> {
const { attributes } = doc;
return {
...doc,
attributes: {
...attributes,
...(attributes.actions
? {
actions: attributes.actions.map((action) => {
if (action.actionTypeId !== '.pagerduty' || action.params.eventAction === 'trigger') {
return action;
}
return {
...action,
params: {
...action.params,
dedupKey: action.params.dedupKey ?? '{{alertId}}',
},
};
}),
}
: {}),
},
};
}

function pipeMigrations(...migrations: AlertMigration[]): AlertMigration {
return (doc: SavedObjectUnsanitizedDoc<RawAlert>) =>
migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc), doc);
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,22 @@ export function getActionType(): ActionTypeModel {
const errors = {
summary: new Array<string>(),
timestamp: new Array<string>(),
dedupKey: new Array<string>(),
};
validationResult.errors = errors;
if (
!actionParams.dedupKey?.length &&
(actionParams.eventAction === 'resolve' || actionParams.eventAction === 'acknowledge')
) {
errors.dedupKey.push(
i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredDedupKeyText',
{
defaultMessage: 'DedupKey is required when resolving or acknowledging an incident.',
}
)
);
}
if (!actionParams.summary?.length) {
errors.summary.push(
i18n.translate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ const PagerDutyParamsFields: React.FunctionComponent<ActionParamsProps<PagerDuty
),
},
];

const isDedupeKeyRequired = eventAction !== 'trigger';

return (
<Fragment>
<EuiFlexGroup>
Expand Down Expand Up @@ -144,12 +147,23 @@ const PagerDutyParamsFields: React.FunctionComponent<ActionParamsProps<PagerDuty
<EuiFlexItem>
<EuiFormRow
fullWidth
label={i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.dedupKeyTextFieldLabel',
{
defaultMessage: 'DedupKey (optional)',
}
)}
error={errors.dedupKey}
isInvalid={errors.dedupKey.length > 0}
label={
isDedupeKeyRequired
? i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.dedupKeyTextRequiredFieldLabel',
{
defaultMessage: 'DedupKey',
}
)
: i18n.translate(
'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.dedupKeyTextFieldLabel',
{
defaultMessage: 'DedupKey (optional)',
}
)
}
>
<TextFieldWithMessageVariables
index={index}
Expand Down
Loading

0 comments on commit 528193f

Please sign in to comment.