-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ResponseOps] Allow users authenticated with an API keys to manage al…
…erting rules (#154189) Resolves #152140 ## Summary Updates the following functions in the Rules Client to re-use the API key in context and avoid having the system invalidate them when no longer in use: - bulk_delete - bulk_edit - clone - create - delete - update - update_api_key Also adds a new field to the rule SO to help determine when whether an api key was created by a user or created by us. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### To verify - Follow these [instructions](https://www.elastic.co/guide/en/kibana/master/api-keys.html#create-api-key) to create an api key. Make sure to copy your api key - Run the following ``` curl -X POST "http://localhost:5601/api/alerting/rule/" -H 'Authorization: ApiKey ${API_KEY}' -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d' { "rule_type_id": "example.pattern", "name": "pattern", "schedule": { "interval": "5s" }, "actions": [ ], "consumer": "alerts", "tags": [], "notify_when": "onActionGroupChange", "params": { "patterns": { "instA": " a - - a " } } }' ``` - Verify that the request returns a rule with`"api_key_created_by_user":true` - Try this with the other rules clients functions listed above to verify that you can manage alerting rules when authenticated with an api key - Verify that `"api_key_created_by_user":false` when you remove the api key header and add `-u ${USERNAME}:${PASSWORD}` to authenticate
- Loading branch information
Showing
70 changed files
with
1,191 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
x-pack/plugins/alerting/server/rules_client/common/api_key_as_alert_attributes.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
* 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 { apiKeyAsAlertAttributes } from './api_key_as_alert_attributes'; | ||
|
||
describe('apiKeyAsAlertAttributes', () => { | ||
test('return attributes', () => { | ||
expect( | ||
apiKeyAsAlertAttributes( | ||
{ | ||
apiKeysEnabled: true, | ||
result: { | ||
id: '123', | ||
name: '123', | ||
api_key: 'abc', | ||
}, | ||
}, | ||
'test', | ||
false | ||
) | ||
).toEqual({ | ||
apiKey: 'MTIzOmFiYw==', | ||
apiKeyOwner: 'test', | ||
apiKeyCreatedByUser: false, | ||
}); | ||
}); | ||
|
||
test('returns null attributes when api keys are not enabled', () => { | ||
expect( | ||
apiKeyAsAlertAttributes( | ||
{ | ||
apiKeysEnabled: false, | ||
}, | ||
'test', | ||
false | ||
) | ||
).toEqual({ | ||
apiKey: null, | ||
apiKeyOwner: null, | ||
apiKeyCreatedByUser: null, | ||
}); | ||
}); | ||
|
||
test('returns apiKeyCreatedByUser as true when createdByUser is passed in', () => { | ||
expect( | ||
apiKeyAsAlertAttributes( | ||
{ | ||
apiKeysEnabled: true, | ||
result: { | ||
id: '123', | ||
name: '123', | ||
api_key: 'abc', | ||
}, | ||
}, | ||
'test', | ||
true | ||
) | ||
).toEqual({ | ||
apiKey: 'MTIzOmFiYw==', | ||
apiKeyOwner: 'test', | ||
apiKeyCreatedByUser: true, | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
x-pack/plugins/alerting/server/rules_client/lib/create_new_api_key_set.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
/* | ||
* 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 { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks'; | ||
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; | ||
import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; | ||
import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; | ||
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; | ||
import { actionsAuthorizationMock } from '@kbn/actions-plugin/server/mocks'; | ||
import { AlertingAuthorization } from '../../authorization/alerting_authorization'; | ||
import { ActionsAuthorization } from '@kbn/actions-plugin/server'; | ||
import { RawRule } from '../../types'; | ||
import { getBeforeSetup, mockedDateString } from '../tests/lib'; | ||
import { createNewAPIKeySet } from './create_new_api_key_set'; | ||
import { RulesClientContext } from '../types'; | ||
|
||
const taskManager = taskManagerMock.createStart(); | ||
const ruleTypeRegistry = ruleTypeRegistryMock.create(); | ||
const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); | ||
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); | ||
const authorization = alertingAuthorizationMock.create(); | ||
const actionsAuthorization = actionsAuthorizationMock.create(); | ||
|
||
const kibanaVersion = 'v8.0.0'; | ||
const rulesClientParams: jest.Mocked<RulesClientContext> = { | ||
taskManager, | ||
ruleTypeRegistry, | ||
unsecuredSavedObjectsClient, | ||
authorization: authorization as unknown as AlertingAuthorization, | ||
actionsAuthorization: actionsAuthorization as unknown as ActionsAuthorization, | ||
spaceId: 'default', | ||
getUserName: jest.fn(), | ||
createAPIKey: jest.fn(), | ||
logger: loggingSystemMock.create().get(), | ||
encryptedSavedObjectsClient: encryptedSavedObjects, | ||
getActionsClient: jest.fn(), | ||
getEventLogClient: jest.fn(), | ||
kibanaVersion, | ||
minimumScheduleInterval: { value: '1m', enforce: false }, | ||
minimumScheduleIntervalInMs: 1, | ||
fieldsToExcludeFromPublicApi: [], | ||
isAuthenticationTypeAPIKey: jest.fn(), | ||
getAuthenticationAPIKey: jest.fn(), | ||
}; | ||
|
||
const username = 'test'; | ||
const attributes: RawRule = { | ||
enabled: true, | ||
name: 'rule-name', | ||
tags: ['tag-1', 'tag-2'], | ||
alertTypeId: '123', | ||
consumer: 'rule-consumer', | ||
legacyId: null, | ||
schedule: { interval: '1s' }, | ||
actions: [], | ||
params: {}, | ||
createdBy: null, | ||
updatedBy: null, | ||
createdAt: mockedDateString, | ||
updatedAt: mockedDateString, | ||
apiKey: null, | ||
apiKeyOwner: null, | ||
throttle: null, | ||
notifyWhen: null, | ||
muteAll: false, | ||
mutedInstanceIds: [], | ||
executionStatus: { | ||
status: 'unknown', | ||
lastExecutionDate: '2020-08-20T19:23:38Z', | ||
error: null, | ||
warning: null, | ||
}, | ||
revision: 0, | ||
}; | ||
|
||
describe('createNewAPIKeySet', () => { | ||
beforeEach(() => { | ||
getBeforeSetup(rulesClientParams, taskManager, ruleTypeRegistry); | ||
}); | ||
|
||
test('create new api keys', async () => { | ||
rulesClientParams.createAPIKey.mockResolvedValueOnce({ | ||
apiKeysEnabled: true, | ||
result: { id: '123', name: '123', api_key: 'abc' }, | ||
}); | ||
const apiKey = await createNewAPIKeySet(rulesClientParams, { | ||
id: attributes.alertTypeId, | ||
ruleName: attributes.name, | ||
username, | ||
shouldUpdateApiKey: true, | ||
}); | ||
expect(apiKey).toEqual({ | ||
apiKey: 'MTIzOmFiYw==', | ||
apiKeyCreatedByUser: undefined, | ||
apiKeyOwner: 'test', | ||
}); | ||
expect(rulesClientParams.createAPIKey).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('should get api key from the request if the user is authenticated using api keys', async () => { | ||
rulesClientParams.getAuthenticationAPIKey.mockReturnValueOnce({ | ||
apiKeysEnabled: true, | ||
result: { id: '123', name: '123', api_key: 'abc' }, | ||
}); | ||
rulesClientParams.isAuthenticationTypeAPIKey.mockReturnValueOnce(true); | ||
const apiKey = await createNewAPIKeySet(rulesClientParams, { | ||
id: attributes.alertTypeId, | ||
ruleName: attributes.name, | ||
username, | ||
shouldUpdateApiKey: true, | ||
}); | ||
expect(apiKey).toEqual({ | ||
apiKey: 'MTIzOmFiYw==', | ||
apiKeyCreatedByUser: true, | ||
apiKeyOwner: 'test', | ||
}); | ||
expect(rulesClientParams.getAuthenticationAPIKey).toHaveBeenCalledTimes(1); | ||
expect(rulesClientParams.isAuthenticationTypeAPIKey).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('should throw an error if getting the api key fails', async () => { | ||
rulesClientParams.createAPIKey.mockRejectedValueOnce(new Error('Test failure')); | ||
await expect( | ||
async () => | ||
await createNewAPIKeySet(rulesClientParams, { | ||
id: attributes.alertTypeId, | ||
ruleName: attributes.name, | ||
username, | ||
shouldUpdateApiKey: true, | ||
}) | ||
).rejects.toThrowErrorMatchingInlineSnapshot( | ||
`"Error creating API key for rule - Test failure"` | ||
); | ||
}); | ||
|
||
test('should throw an error if getting the api key fails and an error message is passed in', async () => { | ||
rulesClientParams.createAPIKey.mockRejectedValueOnce(new Error('Test failure')); | ||
await expect( | ||
async () => | ||
await createNewAPIKeySet(rulesClientParams, { | ||
id: attributes.alertTypeId, | ||
ruleName: attributes.name, | ||
username, | ||
shouldUpdateApiKey: true, | ||
errorMessage: 'Error updating rule: could not create API key', | ||
}) | ||
).rejects.toThrowErrorMatchingInlineSnapshot( | ||
`"Error updating rule: could not create API key - Test failure"` | ||
); | ||
}); | ||
|
||
test('should return null if shouldUpdateApiKey: false', async () => { | ||
const apiKey = await createNewAPIKeySet(rulesClientParams, { | ||
id: attributes.alertTypeId, | ||
ruleName: attributes.name, | ||
username, | ||
shouldUpdateApiKey: false, | ||
}); | ||
expect(apiKey).toEqual({ | ||
apiKey: null, | ||
apiKeyCreatedByUser: null, | ||
apiKeyOwner: null, | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.