Skip to content

Commit c272470

Browse files
[Alerting v2] Consolidate the feature privileges into one place (#270574)
## Summary This PR prepares the ground for the upcoming RBAC work by making it easier to configure and add feature privileges that can be used by the UI and the backend. Also, all API tests were updated to use only the feature privileges they actually require. Closes: elastic/rna-program#442 Closes: elastic/rna-program#439 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [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 --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 4ca595d commit c272470

44 files changed

Lines changed: 659 additions & 232 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type {
9+
FeatureKibanaPrivileges,
10+
SubFeatureConfig,
11+
SubFeaturePrivilegeConfig,
12+
} from '@kbn/features-plugin/common';
13+
import { ACTION_POLICY_SAVED_OBJECT_TYPE, RULE_SAVED_OBJECT_TYPE } from './saved_object_types';
14+
15+
type ValueOf<T> = T[keyof T];
16+
type NestedValueOf<T extends Record<string, Record<string, string>>> = ValueOf<{
17+
[K in keyof T]: ValueOf<T[K]>;
18+
}>;
19+
20+
/**
21+
* Single source of truth for alerting_v2 feature ids, API privilege strings,
22+
* UI capability keys, and future sub-feature definitions.
23+
*
24+
* Add all new alerting_v2 privilege strings here and derive from this file.
25+
*/
26+
export const ALERTING_V2_API_PRIVILEGES = {
27+
rules: {
28+
read: 'read-alerting-v2-rules',
29+
write: 'write-alerting-v2-rules',
30+
},
31+
alerts: {
32+
read: 'read-alerting-v2-alerts',
33+
write: 'write-alerting-v2-alerts',
34+
},
35+
actionPolicies: {
36+
read: 'read-alerting-v2-action-policies',
37+
write: 'write-alerting-v2-action-policies',
38+
},
39+
} as const;
40+
41+
/**
42+
* Top-level UI capability keys per feature. Each feature owns its own
43+
* `all` / `read` capability strings. Granted entries surface at runtime as
44+
* `capabilities[featureId][capabilityKey]`. Extend per-feature when a feature
45+
* needs additional top-level UI flags beyond the primary read/write split.
46+
*/
47+
export const ALERTING_V2_UI_CAPABILITIES = {
48+
rules: {
49+
all: 'all',
50+
read: 'read',
51+
},
52+
alerts: {
53+
all: 'all',
54+
read: 'read',
55+
},
56+
actionPolicies: {
57+
all: 'all',
58+
read: 'read',
59+
},
60+
} as const;
61+
62+
/**
63+
* Sub-feature-only UI capability keys per feature. Add new sub-feature UI
64+
* capabilities here (e.g. `alerts: { assignAlert: 'assignAlert' }`) so the
65+
* sub-feature privilege schema stays type-checked against the catalog.
66+
*/
67+
export const ALERTING_V2_SUB_FEATURE_UI_CAPABILITIES = {
68+
rules: {},
69+
alerts: {},
70+
actionPolicies: {},
71+
} as const satisfies Record<string, Record<string, string>>;
72+
73+
type AlertingV2ApiPrivilege = NestedValueOf<typeof ALERTING_V2_API_PRIVILEGES>;
74+
type AlertingV2TopLevelUICapability = NestedValueOf<typeof ALERTING_V2_UI_CAPABILITIES>;
75+
type AlertingV2SubFeatureUICapability = NestedValueOf<
76+
typeof ALERTING_V2_SUB_FEATURE_UI_CAPABILITIES
77+
>;
78+
type AlertingV2UICapability = AlertingV2TopLevelUICapability | AlertingV2SubFeatureUICapability;
79+
80+
type AlertingV2FeaturePrivilege = Pick<FeatureKibanaPrivileges, 'api' | 'ui' | 'savedObject'> & {
81+
readonly api: readonly AlertingV2ApiPrivilege[];
82+
readonly ui: readonly AlertingV2TopLevelUICapability[];
83+
readonly savedObject: {
84+
readonly all: readonly string[];
85+
readonly read: readonly string[];
86+
};
87+
};
88+
89+
type AlertingV2SubFeaturePrivilege = Omit<
90+
SubFeaturePrivilegeConfig,
91+
'api' | 'ui' | 'savedObject'
92+
> & {
93+
readonly api: readonly AlertingV2ApiPrivilege[];
94+
readonly ui: readonly AlertingV2UICapability[];
95+
readonly savedObject: {
96+
readonly all: readonly string[];
97+
readonly read: readonly string[];
98+
};
99+
};
100+
101+
type AlertingV2SubFeature = Omit<SubFeatureConfig, 'privilegeGroups'> & {
102+
readonly privilegeGroups: ReadonlyArray<{
103+
readonly groupType: 'independent' | 'mutually_exclusive';
104+
readonly privileges: readonly AlertingV2SubFeaturePrivilege[];
105+
}>;
106+
};
107+
108+
export interface AlertingV2FeatureDefinition {
109+
readonly id: string;
110+
readonly name: string;
111+
readonly privileges: {
112+
readonly all: AlertingV2FeaturePrivilege;
113+
readonly read: AlertingV2FeaturePrivilege;
114+
};
115+
readonly subFeatures: readonly AlertingV2SubFeature[];
116+
}
117+
118+
export const ALERTING_V2_FEATURES = {
119+
rules: {
120+
id: 'alerting_v2_rules',
121+
name: 'Rules',
122+
privileges: {
123+
all: {
124+
api: [ALERTING_V2_API_PRIVILEGES.rules.read, ALERTING_V2_API_PRIVILEGES.rules.write],
125+
ui: [ALERTING_V2_UI_CAPABILITIES.rules.all, ALERTING_V2_UI_CAPABILITIES.rules.read],
126+
savedObject: {
127+
all: [RULE_SAVED_OBJECT_TYPE],
128+
read: [],
129+
},
130+
},
131+
read: {
132+
api: [ALERTING_V2_API_PRIVILEGES.rules.read],
133+
ui: [ALERTING_V2_UI_CAPABILITIES.rules.read],
134+
savedObject: {
135+
all: [],
136+
read: [RULE_SAVED_OBJECT_TYPE],
137+
},
138+
},
139+
},
140+
subFeatures: [] as const,
141+
},
142+
alerts: {
143+
id: 'alerting_v2_alerts',
144+
name: 'Alerts',
145+
privileges: {
146+
all: {
147+
api: [ALERTING_V2_API_PRIVILEGES.alerts.read, ALERTING_V2_API_PRIVILEGES.alerts.write],
148+
ui: [ALERTING_V2_UI_CAPABILITIES.alerts.all, ALERTING_V2_UI_CAPABILITIES.alerts.read],
149+
savedObject: {
150+
all: [],
151+
read: [],
152+
},
153+
},
154+
read: {
155+
api: [ALERTING_V2_API_PRIVILEGES.alerts.read],
156+
ui: [ALERTING_V2_UI_CAPABILITIES.alerts.read],
157+
savedObject: {
158+
all: [],
159+
read: [],
160+
},
161+
},
162+
},
163+
subFeatures: [] as const,
164+
},
165+
actionPolicies: {
166+
id: 'alerting_v2_action_policies',
167+
name: 'Action Policies',
168+
privileges: {
169+
all: {
170+
api: [
171+
ALERTING_V2_API_PRIVILEGES.actionPolicies.read,
172+
ALERTING_V2_API_PRIVILEGES.actionPolicies.write,
173+
],
174+
ui: [
175+
ALERTING_V2_UI_CAPABILITIES.actionPolicies.all,
176+
ALERTING_V2_UI_CAPABILITIES.actionPolicies.read,
177+
],
178+
savedObject: {
179+
all: [ACTION_POLICY_SAVED_OBJECT_TYPE],
180+
read: [],
181+
},
182+
},
183+
read: {
184+
api: [ALERTING_V2_API_PRIVILEGES.actionPolicies.read],
185+
ui: [ALERTING_V2_UI_CAPABILITIES.actionPolicies.read],
186+
savedObject: {
187+
all: [],
188+
read: [ACTION_POLICY_SAVED_OBJECT_TYPE],
189+
},
190+
},
191+
},
192+
subFeatures: [] as const,
193+
},
194+
} as const satisfies Record<string, AlertingV2FeatureDefinition>;
195+
196+
export type AlertingV2Feature = keyof typeof ALERTING_V2_FEATURES;
197+
198+
type TopLevelUiOf<F extends AlertingV2Feature> =
199+
| (typeof ALERTING_V2_FEATURES)[F]['privileges']['all']['ui'][number]
200+
| (typeof ALERTING_V2_FEATURES)[F]['privileges']['read']['ui'][number];
201+
202+
type SubFeatureUiOf<F extends AlertingV2Feature> =
203+
(typeof ALERTING_V2_FEATURES)[F]['subFeatures'][number]['privilegeGroups'][number]['privileges'][number]['ui'][number];
204+
205+
export type AlertingV2UICapabilityFor<F extends AlertingV2Feature> =
206+
| TopLevelUiOf<F>
207+
| SubFeatureUiOf<F>;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
export const RULE_SAVED_OBJECT_TYPE = 'alerting_rule';
9+
export const ACTION_POLICY_SAVED_OBJECT_TYPE = 'alerting_action_policy';
10+
export const API_KEY_PENDING_INVALIDATION_TYPE = 'alerting_api_key_pending_invalidation';

x-pack/platform/plugins/shared/alerting_v2/server/lib/security/privileges.ts

Lines changed: 36 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -6,90 +6,54 @@
66
*/
77

88
import type { FeaturesPluginSetup } from '@kbn/features-plugin/server';
9+
import type { KibanaFeatureConfig } from '@kbn/features-plugin/common';
910
import type { AppCategory } from '@kbn/core/types';
1011
import { APP_ID } from '../constants';
11-
import { ACTION_POLICY_SAVED_OBJECT_TYPE, RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
12+
import {
13+
ALERTING_V2_API_PRIVILEGES,
14+
ALERTING_V2_FEATURES,
15+
type AlertingV2FeatureDefinition,
16+
} from '../../../common/feature_privileges';
1217

13-
const ALERTS_FEATURE_ID = 'alerting_v2_alerts';
14-
const RULES_FEATURE_ID = 'alerting_v2_rules';
15-
const CATEGORY_ID = 'alerting';
16-
17-
export const ALERTING_V2_API_PRIVILEGES = {
18-
rules: {
19-
read: 'read-alerting-v2-rules',
20-
write: 'write-alerting-v2-rules',
21-
},
22-
alerts: {
23-
read: 'read-alerting-v2-alerts',
24-
write: 'write-alerting-v2-alerts',
25-
},
26-
actionPolicies: {
27-
read: 'read-alerting-v2-action-policies',
28-
write: 'write-alerting-v2-action-policies',
29-
},
30-
} as const;
31-
32-
const ALERTING_V2_SAVED_OBJECT_TYPES = [
33-
RULE_SAVED_OBJECT_TYPE,
34-
ACTION_POLICY_SAVED_OBJECT_TYPE,
35-
] as const;
36-
37-
const getPrivileges = () => ({
38-
all: {
39-
app: [APP_ID],
40-
savedObject: {
41-
all: [...ALERTING_V2_SAVED_OBJECT_TYPES],
42-
read: [],
43-
},
44-
ui: [],
45-
api: [
46-
ALERTING_V2_API_PRIVILEGES.rules.read,
47-
ALERTING_V2_API_PRIVILEGES.rules.write,
48-
ALERTING_V2_API_PRIVILEGES.alerts.read,
49-
ALERTING_V2_API_PRIVILEGES.alerts.write,
50-
ALERTING_V2_API_PRIVILEGES.actionPolicies.read,
51-
ALERTING_V2_API_PRIVILEGES.actionPolicies.write,
52-
],
53-
},
54-
read: {
55-
app: [APP_ID],
56-
savedObject: {
57-
all: [],
58-
read: [...ALERTING_V2_SAVED_OBJECT_TYPES],
59-
},
60-
ui: [],
61-
api: [
62-
ALERTING_V2_API_PRIVILEGES.rules.read,
63-
ALERTING_V2_API_PRIVILEGES.alerts.read,
64-
ALERTING_V2_API_PRIVILEGES.actionPolicies.read,
65-
],
66-
},
67-
});
18+
export { ALERTING_V2_API_PRIVILEGES };
6819

6920
const category: AppCategory = {
70-
id: CATEGORY_ID,
21+
id: 'alerting',
7122
label: 'Alerting',
7223
order: 1000,
7324
euiIconType: 'watchesApp',
7425
};
7526

76-
const alertsFeature = {
77-
id: ALERTS_FEATURE_ID,
78-
name: 'Alerts',
79-
category,
80-
app: [APP_ID],
81-
privileges: getPrivileges(),
82-
};
83-
84-
const rulesFeature = {
85-
id: RULES_FEATURE_ID,
86-
name: 'Rules',
27+
const buildKibanaFeature = (feature: AlertingV2FeatureDefinition): KibanaFeatureConfig => ({
28+
id: feature.id,
29+
name: feature.name,
8730
category,
8831
app: [APP_ID],
89-
privileges: getPrivileges(),
90-
};
32+
privileges: {
33+
all: {
34+
app: [APP_ID],
35+
savedObject: {
36+
all: [...feature.privileges.all.savedObject.all],
37+
read: [...feature.privileges.all.savedObject.read],
38+
},
39+
api: [...feature.privileges.all.api],
40+
ui: [...feature.privileges.all.ui],
41+
},
42+
read: {
43+
app: [APP_ID],
44+
savedObject: {
45+
all: [...feature.privileges.read.savedObject.all],
46+
read: [...feature.privileges.read.savedObject.read],
47+
},
48+
api: [...feature.privileges.read.api],
49+
ui: [...feature.privileges.read.ui],
50+
},
51+
},
52+
...(feature.subFeatures.length > 0 ? { subFeatures: [...feature.subFeatures] } : {}),
53+
});
9154

9255
export const registerFeaturePrivileges = (features: FeaturesPluginSetup) => {
93-
features.registerKibanaFeature(alertsFeature);
94-
features.registerKibanaFeature(rulesFeature);
56+
Object.values(ALERTING_V2_FEATURES).forEach((feature) => {
57+
features.registerKibanaFeature(buildKibanaFeature(feature));
58+
});
9559
};

x-pack/platform/plugins/shared/alerting_v2/server/routes/action_policies/create_action_policy_route.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ export class CreateActionPolicyRoute extends BaseAlertingRoute {
2626
static path = `${ALERTING_V2_ACTION_POLICY_API_PATH}`;
2727
static security: RouteSecurity = {
2828
authz: {
29-
requiredPrivileges: [ALERTING_V2_API_PRIVILEGES.actionPolicies.write],
29+
requiredPrivileges: [
30+
ALERTING_V2_API_PRIVILEGES.actionPolicies.write,
31+
ALERTING_V2_API_PRIVILEGES.rules.read,
32+
],
3033
},
3134
};
3235
static routeOptions = {

x-pack/platform/plugins/shared/alerting_v2/server/routes/action_policies/upsert_action_policy_route.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ export class UpsertActionPolicyRoute extends BaseAlertingRoute {
3131
static path = `${ALERTING_V2_ACTION_POLICY_API_PATH}/{id}`;
3232
static security: RouteSecurity = {
3333
authz: {
34-
requiredPrivileges: [ALERTING_V2_API_PRIVILEGES.actionPolicies.write],
34+
requiredPrivileges: [
35+
ALERTING_V2_API_PRIVILEGES.actionPolicies.write,
36+
ALERTING_V2_API_PRIVILEGES.rules.read,
37+
],
3538
},
3639
};
3740
static routeOptions = {

x-pack/platform/plugins/shared/alerting_v2/server/routes/suggestions/matcher_data_fields_route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class MatcherDataFieldsRoute extends BaseAlertingRoute {
3030
static path = `${ALERTING_V2_ACTION_POLICY_API_PATH}/suggestions/data_fields`;
3131
static security: RouteSecurity = {
3232
authz: {
33-
requiredPrivileges: [ALERTING_V2_API_PRIVILEGES.actionPolicies.read],
33+
requiredPrivileges: [ALERTING_V2_API_PRIVILEGES.alerts.read],
3434
},
3535
};
3636
static routeOptions = {

x-pack/platform/plugins/shared/alerting_v2/server/routes/suggestions/matcher_value_suggestions_route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export class MatcherValueSuggestionsRoute extends BaseAlertingRoute {
3131
static security: RouteSecurity = {
3232
authz: {
3333
requiredPrivileges: [
34-
ALERTING_V2_API_PRIVILEGES.actionPolicies.read,
3534
ALERTING_V2_API_PRIVILEGES.rules.read,
35+
ALERTING_V2_API_PRIVILEGES.alerts.read,
3636
],
3737
},
3838
};

0 commit comments

Comments
 (0)