Skip to content

Commit

Permalink
[Alerting] Handling connectors with missing secrets during rule creat…
Browse files Browse the repository at this point in the history
…ion and action execution (#98618) (#99226)

* [Connectors][API] Updated connectors with enabledAfterImport flag

* fixed functional tests

* added new field to connectors API docs

* added update unit test

* fixed test

* renamed enableAfterImport to isMissingSecrets

* removed onExport

* revert the logic of true/false for isMissingSecrets

* fixed test

* fixed tests

* added unit test

* fixed docs

* fixed import text and button labels

* fixed import text

* fixed text

* Showing placeholder message when connector is missing secrets

* Throwing error on isMissingSecrets = true before executing actions

* Hiding connectors with missing secrets from dropdown

* Checking for connectors with missing secrets during action validation on rule creation/update

* Updating error wording

Co-authored-by: Yuliia Naumenko <yuliia.naumenko@elastic.com>

Co-authored-by: ymao1 <ying.mao@elastic.co>
Co-authored-by: Yuliia Naumenko <yuliia.naumenko@elastic.com>
  • Loading branch information
3 people authored May 4, 2021
1 parent 0da1d6b commit 078f3e0
Show file tree
Hide file tree
Showing 11 changed files with 411 additions and 273 deletions.
29 changes: 29 additions & 0 deletions x-pack/plugins/actions/server/create_execute_function.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,35 @@ describe('execute()', () => {
);
});

test('throws when isMissingSecrets is true for connector', async () => {
const executeFn = createExecutionEnqueuerFunction({
taskManager: mockTaskManager,
isESOCanEncrypt: true,
actionTypeRegistry: actionTypeRegistryMock.create(),
preconfiguredActions: [],
});
savedObjectsClient.get.mockResolvedValueOnce({
id: '123',
type: 'action',
attributes: {
name: 'mock-action',
isMissingSecrets: true,
actionTypeId: 'mock-action',
},
references: [],
});
await expect(
executeFn(savedObjectsClient, {
id: '123',
params: { baz: false },
spaceId: 'default',
apiKey: null,
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Unable to execute action because no secrets are defined for the \\"mock-action\\" connector."`
);
});

test('should ensure action type is enabled', async () => {
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
const executeFn = createExecutionEnqueuerFunction({
Expand Down
20 changes: 12 additions & 8 deletions x-pack/plugins/actions/server/create_execute_function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,18 @@ export function createExecutionEnqueuerFunction({
);
}

const actionTypeId = await getActionTypeId(
const { actionTypeId, name, isMissingSecrets } = await getAction(
unsecuredSavedObjectsClient,
preconfiguredActions,
id
);

if (isMissingSecrets) {
throw new Error(
`Unable to execute action because no secrets are defined for the "${name}" connector.`
);
}

if (!actionTypeRegistry.isActionExecutable(id, actionTypeId, { notifyUsage: true })) {
actionTypeRegistry.ensureActionTypeEnabled(actionTypeId);
}
Expand Down Expand Up @@ -91,18 +97,16 @@ function executionSourceAsSavedObjectReferences(executionSource: ActionExecutorO
: {};
}

async function getActionTypeId(
async function getAction(
unsecuredSavedObjectsClient: SavedObjectsClientContract,
preconfiguredActions: PreConfiguredAction[],
actionId: string
): Promise<string> {
): Promise<PreConfiguredAction | RawAction> {
const pcAction = preconfiguredActions.find((action) => action.id === actionId);
if (pcAction) {
return pcAction.actionTypeId;
return pcAction;
}

const {
attributes: { actionTypeId },
} = await unsecuredSavedObjectsClient.get<RawAction>('action', actionId);
return actionTypeId;
const { attributes } = await unsecuredSavedObjectsClient.get<RawAction>('action', actionId);
return attributes;
}
34 changes: 30 additions & 4 deletions x-pack/plugins/alerting/server/alerts_client/alerts_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export class AlertsClient {
throw Boom.badRequest(`Error creating rule: could not create API key - ${error.message}`);
}

this.validateActions(alertType, data.actions);
await this.validateActions(alertType, data.actions);

const createTime = Date.now();
const { references, actions } = await this.denormalizeActions(data.actions);
Expand Down Expand Up @@ -728,7 +728,7 @@ export class AlertsClient {
data.params,
alertType.validate?.params
);
this.validateActions(alertType, data.actions);
await this.validateActions(alertType, data.actions);

const { actions, references } = await this.denormalizeActions(data.actions);
const username = await this.getUserName();
Expand Down Expand Up @@ -1434,10 +1434,36 @@ export class AlertsClient {
};
}

private validateActions(
private async validateActions(
alertType: UntypedNormalizedAlertType,
actions: NormalizedAlertAction[]
): void {
): Promise<void> {
if (actions.length === 0) {
return;
}

// check for actions using connectors with missing secrets
const actionsClient = await this.getActionsClient();
const actionIds = [...new Set(actions.map((action) => action.id))];
const actionResults = (await actionsClient.getBulk(actionIds)) || [];
const actionsUsingConnectorsWithMissingSecrets = actionResults.filter(
(result) => result.isMissingSecrets
);

if (actionsUsingConnectorsWithMissingSecrets.length) {
throw Boom.badRequest(
i18n.translate('xpack.alerting.alertsClient.validateActions.misconfiguredConnector', {
defaultMessage: 'Invalid connectors: {groups}',
values: {
groups: actionsUsingConnectorsWithMissingSecrets
.map((connector) => connector.name)
.join(', '),
},
})
);
}

// check for actions with invalid action groups
const { actionGroups: alertTypeActionGroups } = alertType;
const usedAlertActionGroups = actions.map((action) => action.group);
const availableAlertTypeActionGroups = new Set(map(alertTypeActionGroups, 'id'));
Expand Down
Loading

0 comments on commit 078f3e0

Please sign in to comment.