Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RAC] [RBAC] MVP RBAC for alerts as data #100705

Merged
merged 33 commits into from
Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b71edfc
squashed commit
dhurley14 Jun 22, 2021
feb13dc
fix type issues and fix jest tests
dhurley14 Jun 22, 2021
a5ef310
generify util functions (#13)
yctercero Jun 22, 2021
9bfdb42
simplify authorized indices function (#15)
dhurley14 Jun 22, 2021
72eb7ff
removes reference to esArchiver key in config and updates path of esA…
dhurley14 Jun 22, 2021
db36922
[RAC][RBAC] Updates integration tests to use subfeature privileges (#18)
dhurley14 Jun 24, 2021
b802d73
update mocks for ES 'update' call within alerts as data client
dhurley14 Jun 24, 2021
9ec74ff
fixes type check failure
dhurley14 Jun 28, 2021
428ce7d
fix jest unit tests, adds integration tests to be executed in CI
dhurley14 Jun 28, 2021
331c89a
[RAC][RBAC] - add alert document versioning (#17)
yctercero Jun 29, 2021
5473cce
undo accidental changes in core
dhurley14 Jun 30, 2021
3e50386
[RAC] - removed changes to security solution (#19)
yctercero Jun 30, 2021
fac326d
updating jest tests and moving security dependency to optional in rul…
yctercero Jun 30, 2021
6041c90
[RAC] [RBAC] force rule data client to register features (#20)
dhurley14 Jul 1, 2021
5eec936
resolve merge conflicts after rebase with master
dhurley14 Jul 1, 2021
48c59a0
[Alerts as data][RBAC] - Addressing comments around method naming and…
yctercero Jul 7, 2021
0aa3362
remove console log
dhurley14 Jul 7, 2021
7f2da22
joe's awesome test fixes
dhurley14 Jul 7, 2021
17cffca
fix bug where we threw a 500 instead of a 404 when passed an id for a…
dhurley14 Jul 7, 2021
c8882df
removes duplicate version field
dhurley14 Jul 7, 2021
bbf9574
temp fix for secondary version field
dhurley14 Jul 7, 2021
d4a73cd
adds outcome unknown to audit log and fixes bug in functional test sc…
dhurley14 Jul 7, 2021
11ca1b9
removes basic/security and basic/spaces directories and since they we…
dhurley14 Jul 7, 2021
4499075
fix type check
dhurley14 Jul 7, 2021
745d4cb
fixes wrong path for rule registry tests in functional test script
dhurley14 Jul 7, 2021
90e049e
fix test which broke from adding outcome:unknown to audit log on upda…
dhurley14 Jul 7, 2021
76d7282
fix more paths..
dhurley14 Jul 7, 2021
519baf7
updates ml anomaly rule detection engine integration tests
dhurley14 Jul 8, 2021
ed2d5a1
fix ml detections test
dhurley14 Jul 8, 2021
b26756d
remove owner:siem from tests since we are not adding this field to si…
dhurley14 Jul 8, 2021
ecd64e0
adds spaces only tests (#22)
dhurley14 Jul 8, 2021
1ae051c
moves 404-not-found tests to execute for all authorized users
dhurley14 Jul 8, 2021
220a8e9
fix type errors from rebase with master, update index names after cha…
dhurley14 Jul 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
@@ -1,8 +1,9 @@
/*
* 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.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

// Similar to the src/core/server/saved_objects/version/decode_version.ts
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/*
* 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.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/kbn-securitysolution-es-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@

export * from './bad_request_error';
export * from './create_boostrap_index';
export * from './decode_version';
export * from './delete_all_index';
export * from './delete_policy';
export * from './delete_template';
export * from './elasticsearch_client';
export * from './encode_hit_version';
export * from './get_index_aliases';
export * from './get_index_count';
export * from './get_index_exists';
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(),
getAugmentedRuleTypesWithAuthorization: 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,184 @@ describe('AlertingAuthorization', () => {
`);
});
});

describe('getAugmentedRuleTypesWithAuthorization', () => {
const myOtherAppAlertType: RegistryAlertType = {
actionGroups: [],
actionVariables: undefined,
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myOtherAppAlertType',
name: 'myOtherAppAlertType',
producer: 'alerts',
enabledInLicense: true,
isExportable: true,
};
const myAppAlertType: RegistryAlertType = {
actionGroups: [],
actionVariables: undefined,
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'myAppAlertType',
name: 'myAppAlertType',
producer: 'myApp',
enabledInLicense: true,
isExportable: true,
};
const mySecondAppAlertType: RegistryAlertType = {
actionGroups: [],
actionVariables: undefined,
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
recoveryActionGroup: RecoveredActionGroup,
id: 'mySecondAppAlertType',
name: 'mySecondAppAlertType',
producer: 'myApp',
enabledInLicense: true,
isExportable: 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.getAugmentedRuleTypesWithAuthorization(
['myApp'],
[ReadOperations.Find, ReadOperations.Get, WriteOperations.Update],
AlertingAuthorizationEntity.Alert
)
).resolves.toMatchInlineSnapshot(`
Object {
"authorizedRuleTypes": Set {
Object {
"actionGroups": Array [],
"actionVariables": undefined,
"authorizedConsumers": Object {
"myApp": Object {
"all": false,
"read": true,
},
},
"defaultActionGroupId": "default",
"enabledInLicense": true,
"id": "myOtherAppAlertType",
"isExportable": true,
"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.getAugmentedRuleTypesWithAuthorization(
['myApp'],
[ReadOperations.Find, ReadOperations.Get, WriteOperations.Update],
AlertingAuthorizationEntity.Alert
)
).resolves.toMatchInlineSnapshot(`
Object {
"authorizedRuleTypes": Set {
Object {
"actionGroups": Array [],
"actionVariables": undefined,
"authorizedConsumers": Object {
"myApp": Object {
"all": true,
"read": true,
},
},
"defaultActionGroupId": "default",
"enabledInLicense": true,
"id": "myOtherAppAlertType",
"isExportable": true,
"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,41 @@ 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 getAugmentedRuleTypesWithAuthorization(
featureIds: readonly string[],
operations: Array<ReadOperations | WriteOperations>,
authorizationEntity: AlertingAuthorizationEntity
): Promise<{
username?: string;
hasAllRequested: boolean;
authorizedRuleTypes: Set<RegistryAlertTypeWithAuth>;
}> {
return this.augmentRuleTypesWithAuthorization(
this.alertTypeRegistry.list(),
operations,
authorizationEntity,
new Set(featureIds)
);
}

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

Expand Down Expand Up @@ -339,13 +360,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 +385,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 +442,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
Loading