Skip to content

Commit

Permalink
squashed commit
Browse files Browse the repository at this point in the history
WIP - trying to fix integration tests, broken authz for observer user / role

updates authz feature builder to what ying had before we messed it up in our branch

fixes integration tests

add rac api access to apm

adds getIndex functionality which requires the asset name to be passed in, same style as in the rule registry data client, adds update integration tests

fix small merge conflict and update shell script

fix merge conflict in alerting test file

fix most type errors

fix the rest of the type failures

fix integration tests

fix integration tests

fix type error with feature registration in apm

fix integration tests in apm and security solution

fix type checker

fix jest tests for apm

remove console.error statements for eslint

fix type check

update security solution jest tests

cleaning up PR and adding basic unit tests

still need to clean up types in tests and update one test file

fixes snapshot for signals template

fix tests

fix type check failures

update cypress test

undo changes in alert authz class, updates alert privilege in apm feature to 'read', utilizes the 'rule' object available in executor params over querying for the rule SO directly

remove verbose logging from detection api integration tests

fix type

fix jest tests, adds missing mocked rule object to alert executor params

[RAC] [RBAC] adds function to get alerts-as-data index name (#6)

* WIP - test script and route in rule registry to pull index name. I need to test out adding this route within the APM and sec sol plugins specifically and see if they spit back the same .alerts index but with the appropriate asset name despite not providing one.

WIP - DO NOT DELETE THIS CODE

minor cleanup

updates client to require passing in index name, which is now available through the alerts as data client function getAlertsIndex

fix types

* remove outdated comment

update README, adds integration test (skipped) for testing authz with search strategy (#8)

* WIP

* update README, adds integration test (skipped) for testing authz with search strategy

* fix rebase issues

* adds typedoc docs

* adds SKIPPED integration test for timeline search strategy to be unskipped once authorization is added to search strategy

* removes unused references to the rule data client within the rule registry

squashed commit (#11)

* clean up commented out code, update PR per initial comments

* introduce index param to get route again, allowing user to specify index to search

* updating feature privileges UI to allow user to have all, read, none on alerts

Co-authored-by: Yara Tercero <[email protected]>

update tests

WIP - updated shell scripts

fixes scripts

fix update route indexName -> index

Merge pull request #12 from yctercero/rbac_update_tests

Updates tests that were previously failing and addresses some feedback.
  • Loading branch information
dhurley14 authored and yctercero committed Jun 30, 2021
1 parent 5f35d23 commit 65a235e
Show file tree
Hide file tree
Showing 110 changed files with 4,509 additions and 71 deletions.
3 changes: 3 additions & 0 deletions packages/kbn-rule-data-utils/src/technical_field_names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const RULE_NAME = 'rule.name' as const;
const RULE_CATEGORY = 'rule.category' as const;
const TAGS = 'tags' as const;
const PRODUCER = `${ALERT_NAMESPACE}.producer` as const;
const OWNER = `${ALERT_NAMESPACE}.owner` as const;
const ALERT_ID = `${ALERT_NAMESPACE}.id` as const;
const ALERT_UUID = `${ALERT_NAMESPACE}.uuid` as const;
const ALERT_START = `${ALERT_NAMESPACE}.start` as const;
Expand All @@ -40,6 +41,7 @@ const fields = {
RULE_CATEGORY,
TAGS,
PRODUCER,
OWNER,
ALERT_ID,
ALERT_UUID,
ALERT_START,
Expand All @@ -62,6 +64,7 @@ export {
RULE_CATEGORY,
TAGS,
PRODUCER,
OWNER,
ALERT_ID,
ALERT_UUID,
ALERT_START,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ const createAlertingAuthorizationMock = () => {
ensureAuthorized: jest.fn(),
filterByRuleTypeAuthorization: jest.fn(),
getFindAuthorizationFilter: jest.fn(),
getAugmentRuleTypesWithAuthorization: jest.fn(),
};
return mocked;
};

export const alertingAuthorizationMock: {
create: () => AlertingAuthorizationMock;
create: () => jest.Mocked<PublicMethodsOf<AlertingAuthorization>>;
} = {
create: createAlertingAuthorizationMock,
};
Original file line number Diff line number Diff line change
Expand Up @@ -1944,4 +1944,169 @@ describe('AlertingAuthorization', () => {
`);
});
});

describe('getAugmentRuleTypesWithAuthorization', () => {
const myOtherAppAlertType: RegistryAlertType = {
actionGroups: [],
actionVariables: undefined,
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myOtherAppAlertType',
name: 'myOtherAppAlertType',
producer: 'alerts',
enabledInLicense: true,
};
const myAppAlertType: RegistryAlertType = {
actionGroups: [],
actionVariables: undefined,
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myAppAlertType',
name: 'myAppAlertType',
producer: 'myApp',
enabledInLicense: true,
};
const mySecondAppAlertType: RegistryAlertType = {
actionGroups: [],
actionVariables: undefined,
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'mySecondAppAlertType',
name: 'mySecondAppAlertType',
producer: 'myApp',
enabledInLicense: true,
};
const setOfAlertTypes = new Set([myAppAlertType, myOtherAppAlertType, mySecondAppAlertType]);

test('it returns authorized rule types given a set of feature ids', async () => {
const { authorization } = mockSecurity();
const checkPrivileges: jest.MockedFunction<
ReturnType<typeof authorization.checkPrivilegesDynamicallyWithRequest>
> = jest.fn();
authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges);
checkPrivileges.mockResolvedValueOnce({
username: 'some-user',
hasAllRequested: false,
privileges: {
kibana: [
{
privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'alert', 'find'),
authorized: true,
},
],
},
});
const alertAuthorization = new AlertingAuthorization({
request,
authorization,
alertTypeRegistry,
features,
auditLogger,
getSpace,
exemptConsumerIds,
});
alertTypeRegistry.list.mockReturnValue(setOfAlertTypes);

await expect(alertAuthorization.getAugmentRuleTypesWithAuthorization(['myApp'])).resolves
.toMatchInlineSnapshot(`
Object {
"authorizedRuleTypes": Set {
Object {
"actionGroups": Array [],
"actionVariables": undefined,
"authorizedConsumers": Object {
"myApp": Object {
"all": false,
"read": true,
},
},
"defaultActionGroupId": "default",
"enabledInLicense": true,
"id": "myOtherAppAlertType",
"minimumLicenseRequired": "basic",
"name": "myOtherAppAlertType",
"producer": "alerts",
"recoveryActionGroup": Object {
"id": "recovered",
"name": "Recovered",
},
},
},
"hasAllRequested": false,
"username": "some-user",
}
`);
});

test('it returns all authorized if user has read, get and update alert privileges', async () => {
const { authorization } = mockSecurity();
const checkPrivileges: jest.MockedFunction<
ReturnType<typeof authorization.checkPrivilegesDynamicallyWithRequest>
> = jest.fn();
authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges);
checkPrivileges.mockResolvedValueOnce({
username: 'some-user',
hasAllRequested: false,
privileges: {
kibana: [
{
privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'alert', 'find'),
authorized: true,
},
{
privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'alert', 'get'),
authorized: true,
},
{
privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'alert', 'update'),
authorized: true,
},
],
},
});
const alertAuthorization = new AlertingAuthorization({
request,
authorization,
alertTypeRegistry,
features,
auditLogger,
getSpace,
exemptConsumerIds,
});
alertTypeRegistry.list.mockReturnValue(setOfAlertTypes);

await expect(alertAuthorization.getAugmentRuleTypesWithAuthorization(['myApp'])).resolves
.toMatchInlineSnapshot(`
Object {
"authorizedRuleTypes": Set {
Object {
"actionGroups": Array [],
"actionVariables": undefined,
"authorizedConsumers": Object {
"myApp": Object {
"all": true,
"read": true,
},
},
"defaultActionGroupId": "default",
"enabledInLicense": true,
"id": "myOtherAppAlertType",
"minimumLicenseRequired": "basic",
"name": "myOtherAppAlertType",
"producer": "alerts",
"recoveryActionGroup": Object {
"id": "recovered",
"name": "Recovered",
},
},
},
"hasAllRequested": false,
"username": "some-user",
}
`);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,39 @@ export class AlertingAuthorization {
return new Set();
});

this.allPossibleConsumers = this.featuresIds.then((featuresIds) =>
featuresIds.size
this.allPossibleConsumers = this.featuresIds.then((featuresIds) => {
return featuresIds.size
? asAuthorizedConsumers([...this.exemptConsumerIds, ...featuresIds], {
read: true,
all: true,
})
: {}
);
: {};
});
}

private shouldCheckAuthorization(): boolean {
return this.authorization?.mode?.useRbacForRequest(this.request) ?? false;
}

/*
* This method exposes the private 'augmentRuleTypesWithAuthorization' to be
* used by the RAC/Alerts client
*/
public async getAugmentRuleTypesWithAuthorization(
featureIds: string[]
): Promise<{
username?: string;
hasAllRequested: boolean;
authorizedRuleTypes: Set<RegistryAlertTypeWithAuth>;
}> {
return this.augmentRuleTypesWithAuthorization(
this.alertTypeRegistry.list(),
[ReadOperations.Find, ReadOperations.Get, WriteOperations.Update],
AlertingAuthorizationEntity.Alert,
new Set(featureIds)
);
}

public async ensureAuthorized({ ruleTypeId, consumer, operation, entity }: EnsureAuthorizedOpts) {
const { authorization } = this;

Expand Down Expand Up @@ -339,13 +358,14 @@ export class AlertingAuthorization {
private async augmentRuleTypesWithAuthorization(
ruleTypes: Set<RegistryAlertType>,
operations: Array<ReadOperations | WriteOperations>,
authorizationEntity: AlertingAuthorizationEntity
authorizationEntity: AlertingAuthorizationEntity,
featuresIds?: Set<string>
): Promise<{
username?: string;
hasAllRequested: boolean;
authorizedRuleTypes: Set<RegistryAlertTypeWithAuth>;
}> {
const featuresIds = await this.featuresIds;
const fIds = featuresIds ?? (await this.featuresIds);
if (this.authorization && this.shouldCheckAuthorization()) {
const checkPrivileges = this.authorization.checkPrivilegesDynamicallyWithRequest(
this.request
Expand All @@ -363,7 +383,7 @@ export class AlertingAuthorization {
// as we can't ask ES for the user's individual privileges we need to ask for each feature
// and ruleType in the system whether this user has this privilege
for (const ruleType of ruleTypesWithAuthorization) {
for (const feature of featuresIds) {
for (const feature of fIds) {
for (const operation of operations) {
privilegeToRuleType.set(
this.authorization!.actions.alerting.get(
Expand Down Expand Up @@ -420,7 +440,7 @@ export class AlertingAuthorization {
return {
hasAllRequested: true,
authorizedRuleTypes: this.augmentWithAuthorizedConsumers(
new Set([...ruleTypes].filter((ruleType) => featuresIds.has(ruleType.producer))),
new Set([...ruleTypes].filter((ruleType) => fIds.has(ruleType.producer))),
await this.allPossibleConsumers
),
};
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/alerting/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ export { FindResult } from './alerts_client';
export { PublicAlertInstance as AlertInstance } from './alert_instance';
export { parseDuration } from './lib';
export { getEsErrorMessage } from './lib/errors';
export {
ReadOperations,
AlertingAuthorizationFilterType,
AlertingAuthorization,
WriteOperations,
AlertingAuthorizationEntity,
} from './authorization';

export const plugin = (initContext: PluginInitializerContext) => new AlertingPlugin(initContext);

Expand Down
10 changes: 6 additions & 4 deletions x-pack/plugins/apm/common/alert_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type { ValuesType } from 'utility-types';
import type { ActionGroup } from '../../alerting/common';
import { ANOMALY_SEVERITY, ANOMALY_THRESHOLD } from './ml_constants';

export const APM_SERVER_FEATURE_ID = 'apm';

export enum AlertType {
ErrorCount = 'apm.error_rate', // ErrorRate was renamed to ErrorCount but the key is kept as `error_rate` for backwards-compat.
TransactionErrorRate = 'apm.transaction_error_rate',
Expand Down Expand Up @@ -44,7 +46,7 @@ export const ALERT_TYPES_CONFIG: Record<
actionGroups: [THRESHOLD_MET_GROUP],
defaultActionGroupId: THRESHOLD_MET_GROUP_ID,
minimumLicenseRequired: 'basic',
producer: 'apm',
producer: APM_SERVER_FEATURE_ID,
isExportable: true,
},
[AlertType.TransactionDuration]: {
Expand All @@ -54,7 +56,7 @@ export const ALERT_TYPES_CONFIG: Record<
actionGroups: [THRESHOLD_MET_GROUP],
defaultActionGroupId: THRESHOLD_MET_GROUP_ID,
minimumLicenseRequired: 'basic',
producer: 'apm',
producer: APM_SERVER_FEATURE_ID,
isExportable: true,
},
[AlertType.TransactionDurationAnomaly]: {
Expand All @@ -64,7 +66,7 @@ export const ALERT_TYPES_CONFIG: Record<
actionGroups: [THRESHOLD_MET_GROUP],
defaultActionGroupId: THRESHOLD_MET_GROUP_ID,
minimumLicenseRequired: 'basic',
producer: 'apm',
producer: APM_SERVER_FEATURE_ID,
isExportable: true,
},
[AlertType.TransactionErrorRate]: {
Expand All @@ -74,7 +76,7 @@ export const ALERT_TYPES_CONFIG: Record<
actionGroups: [THRESHOLD_MET_GROUP],
defaultActionGroupId: THRESHOLD_MET_GROUP_ID,
minimumLicenseRequired: 'basic',
producer: 'apm',
producer: APM_SERVER_FEATURE_ID,
isExportable: true,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import React, { useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
import { AlertType } from '../../../../common/alert_types';
import {
AlertType,
APM_SERVER_FEATURE_ID,
} from '../../../../common/alert_types';
import { getInitialAlertValues } from '../get_initial_alert_values';
import { ApmPluginStartDeps } from '../../../plugin';
interface Props {
Expand All @@ -31,7 +34,7 @@ export function AlertingFlyout(props: Props) {
() =>
alertType &&
services.triggersActionsUi.getAddAlertFlyout({
consumer: 'apm',
consumer: APM_SERVER_FEATURE_ID,
onClose: onCloseAddFlyout,
alertTypeId: alertType,
canChangeTrigger: false,
Expand Down
Loading

0 comments on commit 65a235e

Please sign in to comment.