From 071496501684dff4292a7ac5bf7d49136af9e4eb Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Fri, 4 Sep 2020 19:52:44 +0100 Subject: [PATCH] added test for global read user --- .../fixtures/plugins/alerts/kibana.json | 2 +- .../fixtures/plugins/alerts/server/plugin.ts | 6 +- .../fixtures/plugins/alerts/server/routes.ts | 146 ++++++++++-------- .../common/lib/alert_utils.ts | 4 +- .../tests/alerting/index.ts | 1 - .../tests/alerting/rbac_legacy.ts | 62 ++++---- .../es_archives/alerts_legacy/data.json | 94 +++++++++++ 7 files changed, 214 insertions(+), 101 deletions(-) diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json index 192c4b031a4b37..d8838133f4057a 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/kibana.json @@ -3,7 +3,7 @@ "version": "1.0.0", "kibanaVersion": "kibana", "configPath": ["xpack"], - "requiredPlugins": ["taskManager", "features", "actions", "alerts", "encryptedSavedObjects"], + "requiredPlugins": ["taskManager", "features", "actions", "alerts", "security", "encryptedSavedObjects"], "optionalPlugins": ["spaces"], "server": true, "ui": false diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts index 3668092dd0b5be..3ffac37aba17eb 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts @@ -13,12 +13,14 @@ import { defineAlertTypes } from './alert_types'; import { defineActionTypes } from './action_types'; import { defineRoutes } from './routes'; import { SpacesPluginSetup } from '../../../../../../../plugins/spaces/server'; +import { SecurityPluginSetup } from '../../../../../../../plugins/security/server'; export interface FixtureSetupDeps { features: FeaturesPluginSetup; actions: ActionsPluginSetup; alerts: AlertingPluginSetup; spaces?: SpacesPluginSetup; + security?: SecurityPluginSetup; } export interface FixtureStartDeps { @@ -28,7 +30,7 @@ export interface FixtureStartDeps { export class FixturePlugin implements Plugin { public setup( core: CoreSetup, - { features, actions, alerts, spaces }: FixtureSetupDeps + { features, actions, alerts, spaces, security }: FixtureSetupDeps ) { features.registerFeature({ id: 'alertsFixture', @@ -102,7 +104,7 @@ export class FixturePlugin implements Plugin, - { spaces }: Partial + { spaces, security }: Partial ) { const router = core.http.createRouter(); + router.put( + { + path: '/api/alerts_fixture/{id}/replace_api_key', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: schema.object({ + spaceId: schema.maybe(schema.string()), + }), + }, + }, + async ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> => { + const { id } = req.params; + + if (!security) { + return res.ok({ + body: {}, + }); + } + + const [{ savedObjects }, { encryptedSavedObjects }] = await core.getStartServices(); + const encryptedSavedObjectsWithAlerts = await encryptedSavedObjects.getClient({ + includedHiddenTypes: ['alert'], + }); + const savedObjectsWithAlerts = await savedObjects.getScopedClient(req, { + excludedWrappers: ['security', 'spaces'], + includedHiddenTypes: ['alert'], + }); + + let namespace: string | undefined; + if (spaces && req.body.spaceId) { + namespace = spaces.spacesService.spaceIdToNamespace(req.body.spaceId); + } + + const user = await security.authc.getCurrentUser(req); + if (!user) { + return res.internalError({}); + } + + // Create an API key using the new grant API - in this case the Kibana system user is creating the + // API key for the user, instead of having the user create it themselves, which requires api_key + // privileges + const createAPIKeyResult = await security.authc.grantAPIKeyAsInternalUser(req, { + name: `alert:migrated-to-7.10:${user.username}`, + role_descriptors: {}, + }); + + if (!createAPIKeyResult) { + return res.internalError({}); + } + + const result = await savedObjectsWithAlerts.update( + 'alert', + id, + { + ...( + await encryptedSavedObjectsWithAlerts.getDecryptedAsInternalUser( + 'alert', + id, + { + namespace, + } + ) + ).attributes, + apiKey: Buffer.from(`${createAPIKeyResult.id}:${createAPIKeyResult.api_key}`).toString( + 'base64' + ), + apiKeyOwner: user.username, + }, + { + namespace, + } + ); + return res.ok({ body: result }); + } + ); + router.put( { path: '/api/alerts_fixture/saved_object/{type}/{id}', @@ -94,66 +176,4 @@ export function defineRoutes( return res.ok({ body: result }); } ); - - router.put( - { - path: '/api/alerts_fixture/swap_api_keys/from/{apiKeyFromId}/to/{apiKeyToId}', - validate: { - params: schema.object({ - apiKeyFromId: schema.string(), - apiKeyToId: schema.string(), - }), - body: schema.object({ - spaceId: schema.maybe(schema.string()), - }), - }, - }, - async ( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ): Promise> => { - const { apiKeyFromId, apiKeyToId } = req.params; - - let namespace: string | undefined; - if (spaces && req.body.spaceId) { - namespace = spaces.spacesService.spaceIdToNamespace(req.body.spaceId); - } - const [{ savedObjects }, { encryptedSavedObjects }] = await core.getStartServices(); - const encryptedSavedObjectsWithAlerts = await encryptedSavedObjects.getClient({ - includedHiddenTypes: ['alert'], - }); - const savedObjectsWithAlerts = await savedObjects.getScopedClient(req, { - excludedWrappers: ['security', 'spaces'], - includedHiddenTypes: ['alert'], - }); - - const [fromAlert, toAlert] = await Promise.all([ - encryptedSavedObjectsWithAlerts.getDecryptedAsInternalUser( - 'alert', - apiKeyFromId, - { - namespace, - } - ), - savedObjectsWithAlerts.get('alert', apiKeyToId, { - namespace, - }), - ]); - - const result = await savedObjectsWithAlerts.update( - 'alert', - apiKeyToId, - { - ...toAlert.attributes, - apiKey: fromAlert.attributes.apiKey, - apiKeyOwner: fromAlert.attributes.apiKeyOwner, - }, - { - namespace, - } - ); - return res.ok({ body: result }); - } - ); } diff --git a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts index 5d8995d662fc27..797769fd64471c 100644 --- a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts +++ b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts @@ -271,9 +271,9 @@ export class AlertUtils { return response; } - public swapApiKeys(apiKeyFromId: string, apiKeyToId: string) { + public replaceApiKeys(id: string) { let request = this.supertestWithoutAuth - .put(`/api/alerts_fixture/swap_api_keys/from/${apiKeyFromId}/to/${apiKeyToId}`) + .put(`/api/alerts_fixture/${id}/replace_api_key`) .set('kbn-xsrf', 'foo'); if (this.user) { request = request.auth(this.user.username, this.user.password); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts index 1f0ff23c59c60a..9dbfebff147202 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts @@ -28,6 +28,5 @@ export default function alertingTests({ loadTestFile }: FtrProviderContext) { // note that this test will destroy existing spaces loadTestFile(require.resolve('./rbac_legacy')); - // loadTestFile(require.resolve('./CREATE_DATA')); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts index 0f5fd1e045b292..3315255885ca56 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts @@ -26,6 +26,7 @@ export default function alertTests({ getService }: FtrProviderContext) { space_1_all_alerts_none_actions: '6ee9630a-a20e-44af-9465-217a3717d2ab', space_1_all_with_restricted_fixture: '5cc59319-74ee-4edc-8646-a79ea91067cd', space_1_all: 'd41a6abb-b93b-46df-a80a-926221ea847c', + global_read: '362e362b-a137-4aa2-9434-43e3d0d84a34', superuser: 'b384be60-ec53-4b26-857e-0253ee55b277', }; @@ -40,15 +41,12 @@ export default function alertTests({ getService }: FtrProviderContext) { await es.indices.create({ index: authorizationIndex }); await setupSpacesAndUsers(spacesService, securityService); }); - beforeEach(async () => {}); after(async () => { - objectRemover.removeAll(); await esTestIndexTool.destroy(); await es.indices.delete({ index: authorizationIndex }); await esArchiver.unload('alerts_legacy'); }); - afterEach(async () => {}); for (const scenario of UserAtSpaceScenarios) { const { user, space } = scenario; @@ -73,7 +71,6 @@ export default function alertTests({ getService }: FtrProviderContext) { switch (scenario.id) { case 'no_kibana_privileges at space1': case 'space_1_all at space2': - case 'global_read at space1': // These cases are not relevant as we're testing the migration of alerts which // were valid pre 7.10.0 and which become invalid after the introduction of RBAC in 7.10.0 // these cases were invalid pre 7.10.0 and remain invalid post 7.10.0 @@ -89,15 +86,35 @@ export default function alertTests({ getService }: FtrProviderContext) { await updateAlertSoThatItIsNoLongerLegacy(migratedAlertId); - // console.log( - // 'update alert as user with privileges - so it is no longer a legacy alert' - // ); // update alert as user with privileges - so it is no longer a legacy alert const updatedKeyResponse = await alertUtils.getUpdateApiKeyRequest(migratedAlertId); expect(updatedKeyResponse.statusCode).to.eql(204); await ensureAlertIsRunning(); break; + case 'global_read at space1': + await ensureLegacyAlertHasBeenMigrated(migratedAlertId); + + await updateMigratedAlertToUseApiKeyOfCurrentUser(migratedAlertId); + + await ensureAlertIsRunning(); + + await updateAlertSoThatItIsNoLongerLegacy(migratedAlertId); + + // attempt to update alert as user with no Alerts privileges - as it is no longer a legacy alert + // this should fail, as the user doesn't have the `updateApiKey` privilege for Alerts + const failedUpdateKeyDueToAlertsPrivilegesResponse = await alertUtils.getUpdateApiKeyRequest( + migratedAlertId + ); + + expect(failedUpdateKeyDueToAlertsPrivilegesResponse.statusCode).to.eql(403); + expect(failedUpdateKeyDueToAlertsPrivilegesResponse.body).to.eql({ + error: 'Forbidden', + message: + 'Unauthorized to updateApiKey a "test.always-firing" alert for "alertsFixture"', + statusCode: 403, + }); + break; case 'space_1_all_alerts_none_actions at space1': await ensureLegacyAlertHasBeenMigrated(migratedAlertId); @@ -109,12 +126,12 @@ export default function alertTests({ getService }: FtrProviderContext) { // attempt to update alert as user with no Actions privileges - as it is no longer a legacy alert // this should fail, as the user doesn't have the `execute` privilege for Actions - const failedUpdateKeyResponse = await alertUtils.getUpdateApiKeyRequest( + const failedUpdateKeyDueToActionsPrivilegesResponse = await alertUtils.getUpdateApiKeyRequest( migratedAlertId ); - expect(failedUpdateKeyResponse.statusCode).to.eql(403); - expect(failedUpdateKeyResponse.body).to.eql({ + expect(failedUpdateKeyDueToActionsPrivilegesResponse.statusCode).to.eql(403); + expect(failedUpdateKeyDueToActionsPrivilegesResponse.body).to.eql({ error: 'Forbidden', message: 'Unauthorized to execute actions', statusCode: 403, @@ -125,7 +142,6 @@ export default function alertTests({ getService }: FtrProviderContext) { } async function ensureLegacyAlertHasBeenMigrated(alertId: string) { - // console.log(`ensureLegacyAlertHasBeenMigrated(${alertId})`); const getResponse = await supertestWithoutAuth .get(`${getUrlPrefix(space.id)}/api/alerts/alert/${alertId}`) .auth(user.username, user.password); @@ -133,14 +149,8 @@ export default function alertTests({ getService }: FtrProviderContext) { } async function updateMigratedAlertToUseApiKeyOfCurrentUser(alertId: string) { - // console.log(`updateMigratedAlertToUseApiKeyOfCurrentUser(${alertId})`); - const createNoopAlertResponse = await alertUtils.createNoopAlert({}); - - // swap out api key to run as user with no Actions privileges - const swapResponse = await alertUtils.swapApiKeys( - createNoopAlertResponse.body.id, - alertId - ); + // swap out api key to run as the current user + const swapResponse = await alertUtils.replaceApiKeys(alertId); expect(swapResponse.statusCode).to.eql(200); // ensure the alert is till marked as legacy despite the update of the Api key // this is important as proper update *should* update the legacy status of the alert @@ -152,19 +162,16 @@ export default function alertTests({ getService }: FtrProviderContext) { // otherwise this test will stall for 5 minutes // no other attributes are touched, only runAt, so unless it would have ran when runAt expired, it // won't run now - const taskRescheduleResult = await supertest + await supertest .put(`${getUrlPrefix(space.id)}/api/alerts_fixture/${alertId}/reschedule_task`) .set('kbn-xsrf', 'foo') .send({ runAt: getRunAt(2000), }) .expect(200); - - // console.log(JSON.stringify(taskRescheduleResult.body)); } async function ensureAlertIsRunning() { - // console.log(`ensureAlertIsRunning(${reference})`); // ensure the alert still runs and that it can schedule actions const numberOfAlertExecutions = ( await esTestIndexTool.search('alert:test.always-firing', reference) @@ -194,7 +201,6 @@ export default function alertTests({ getService }: FtrProviderContext) { } async function updateAlertSoThatItIsNoLongerLegacy(alertId: string) { - // console.log(`updateAlertSoThatItIsNoLongerLegacy(${alertId})`); // update the alert as super user (to avoid privilege limitations) so that it is no longer a legacy alert await alertUtils.updateAlwaysFiringAction({ alertId, @@ -207,14 +213,6 @@ export default function alertTests({ getService }: FtrProviderContext) { throttle: '2s', }, }); - // return supertest - // .put(`${getUrlPrefix(space.id)}/api/alerts/alert/${alertId}`) - // .set('kbn-xsrf', 'foo') - // .auth(Superuser.username, Superuser.password) - // .send({ - // name: 'Updated Alert', - // }) - // .expect(200); } }); }); diff --git a/x-pack/test/functional/es_archives/alerts_legacy/data.json b/x-pack/test/functional/es_archives/alerts_legacy/data.json index 11e5e0cdc3f059..770e8e7c156177 100644 --- a/x-pack/test/functional/es_archives/alerts_legacy/data.json +++ b/x-pack/test/functional/es_archives/alerts_legacy/data.json @@ -394,4 +394,98 @@ "updated_at": "2020-09-04T11:51:05.794Z" } } +} + +{ + "type": "doc", + "value": { + "id": "space1:alert:362e362b-a137-4aa2-9434-43e3d0d84a34", + "index": ".kibana_1", + "source": { + "alert": { + "actions": [ + { + "actionRef": "action_0", + "actionTypeId": "test.index-record", + "group": "default", + "params": { + "index": ".kibana-alerting-test-data", + "message": "alertId: {{alertId}},\nalertName: {{alertName}},\nspaceId: {{spaceId}},\ntags: {{tags}},\nalertInstanceId: {{alertInstanceId}},\ninstanceContextValue: {{context.instanceContextValue}},\ninstanceStateValue: {{state.instanceStateValue}}", + "reference": "alert:migrated-to-7.10:global_read" + } + } + ], + "alertTypeId": "test.always-firing", + "apiKey": "asFMeAR9MRRO5bwGiA5z3arqWOs9ZVPHYTvLL2S9JFRZSsA88yauEPxHeri3UpHWqLktDbOSpR4C1bmIQySDt1VOpe13JzGC/Z+/S0I9PaFQ8oHQ2VqjQ26j9hxuS+B9sXtAOhgYu2WBQw1ugeW2Eo86c9wpFux4p3SbKiNhwyC/9WUR5qI63cBnvRs8Zi8ePOMdnTJoiBzduw==", + "apiKeyOwner": "superuser", + "consumer": "alertsFixture", + "createdAt": "2020-09-04T11:50:56.513Z", + "createdBy": "superuser", + "enabled": true, + "muteAll": false, + "mutedInstanceIds": [ + ], + "name": "abc", + "params": { + "index": ".kibana-alerting-test-data", + "reference": "alert:migrated-to-7.10:global_read" + }, + "schedule": { + "interval": "2s" + }, + "scheduledTaskId": "e4df5430-eea4-11ea-a285-352ee3aecffa", + "tags": [ + "tag-A", + "tag-B" + ], + "throttle": "2s", + "updatedBy": "superuser" + }, + "migrationVersion": { + "alert": "7.9.0" + }, + "namespace": "space1", + "references": [ + { + "id": "17f38826-5a8d-4a76-975a-b496e7fffe0b", + "name": "action_0", + "type": "action" + } + ], + "type": "alert", + "updated_at": "2020-09-04T11:50:57.048Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "task:e4df5430-eea4-11ea-a285-352ee3aecffa", + "index": ".kibana_task_manager_1", + "source": { + "migrationVersion": { + "task": "7.6.0" + }, + "references": [ + ], + "task": { + "attempts": 1, + "ownerId": "kibana:5b2de169-2785-441b-ae8c-186a1936b17d", + "params": "{\"alertId\":\"362e362b-a137-4aa2-9434-43e3d0d84a34\",\"spaceId\":\"space1\"}", + "retryAt": "2020-09-04T12:01:05.793Z", + "runAt": "2020-09-04T11:51:04.804Z", + "scheduledAt": "2020-09-04T11:50:57.011Z", + "scope": [ + "alerting" + ], + "startedAt": "2020-09-04T11:51:05.793Z", + "state": "{\"previousStartedAt\":null,\"alertTypeState\":{},\"alertInstances\":{}}", + "status": "running", + "taskType": "alerting:test.always-firing" + }, + "type": "task", + "updated_at": "2020-09-04T11:51:05.794Z" + } + } } \ No newline at end of file