Alerting: Fix permissions for silences list view (#88908)

This commit is contained in:
Sonia Aguilar
2024-06-07 18:19:28 +02:00
committed by GitHub
parent 8aa1bbe27c
commit b761153812
12 changed files with 99 additions and 33 deletions

View File

@@ -396,8 +396,15 @@ func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.Na
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Notification policies", SubTitle: "Determine how alerts are routed to contact points", Id: "am-routes", Url: s.cfg.AppSubURL + "/alerting/routes", Icon: "sitemap"}) alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Notification policies", SubTitle: "Determine how alerts are routed to contact points", Id: "am-routes", Url: s.cfg.AppSubURL + "/alerting/routes", Icon: "sitemap"})
} }
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingInstanceRead), ac.EvalPermission(ac.ActionAlertingInstancesExternalRead))) { if hasAccess(ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingInstanceRead),
ac.EvalPermission(ac.ActionAlertingInstancesExternalRead),
ac.EvalPermission(ac.ActionAlertingSilencesRead),
)) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Silences", SubTitle: "Stop notifications from one or more alerting rules", Id: "silences", Url: s.cfg.AppSubURL + "/alerting/silences", Icon: "bell-slash"}) alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Silences", SubTitle: "Stop notifications from one or more alerting rules", Id: "silences", Url: s.cfg.AppSubURL + "/alerting/silences", Icon: "bell-slash"})
}
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionAlertingInstanceRead), ac.EvalPermission(ac.ActionAlertingInstancesExternalRead))) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Alert groups", SubTitle: "See grouped alerts from an Alertmanager instance", Id: "groups", Url: s.cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"}) alertChildNavs = append(alertChildNavs, &navtree.NavLink{Text: "Alert groups", SubTitle: "See grouped alerts from an Alertmanager instance", Id: "groups", Url: s.cfg.AppSubURL + "/alerting/groups", Icon: "layer-group"})
} }

View File

@@ -62,6 +62,7 @@ export function getAlertingRoutes(cfg = config): RouteDescriptor[] {
roles: evaluateAccess([ roles: evaluateAccess([
AccessControlAction.AlertingInstanceRead, AccessControlAction.AlertingInstanceRead,
AccessControlAction.AlertingInstancesExternalRead, AccessControlAction.AlertingInstancesExternalRead,
AccessControlAction.AlertingSilenceRead,
]), ]),
component: importAlertingComponent( component: importAlertingComponent(
() => import(/* webpackChunkName: "AlertSilences" */ 'app/features/alerting/unified/Silences') () => import(/* webpackChunkName: "AlertSilences" */ 'app/features/alerting/unified/Silences')

View File

@@ -81,18 +81,6 @@ const ui = {
}, },
}; };
const resetMocks = () => {
jest.resetAllMocks();
grantUserPermissions([
AccessControlAction.AlertingInstanceRead,
AccessControlAction.AlertingInstanceCreate,
AccessControlAction.AlertingInstanceUpdate,
AccessControlAction.AlertingInstancesExternalRead,
AccessControlAction.AlertingInstancesExternalWrite,
]);
};
const setUserLogged = (isLogged: boolean) => { const setUserLogged = (isLogged: boolean) => {
config.bootData.user.isSignedIn = isLogged; config.bootData.user.isSignedIn = isLogged;
config.bootData.user.name = isLogged ? 'admin' : ''; config.bootData.user.name = isLogged ? 'admin' : '';
@@ -115,12 +103,18 @@ const server = setupMswServer();
beforeEach(() => { beforeEach(() => {
setupDataSources(dataSources.am, dataSources[MOCK_DATASOURCE_NAME_BROKEN_ALERTMANAGER]); setupDataSources(dataSources.am, dataSources[MOCK_DATASOURCE_NAME_BROKEN_ALERTMANAGER]);
grantUserPermissions([
AccessControlAction.AlertingInstanceRead,
AccessControlAction.AlertingInstanceCreate,
AccessControlAction.AlertingInstanceUpdate,
AccessControlAction.AlertingInstancesExternalRead,
AccessControlAction.AlertingInstancesExternalWrite,
]);
}); });
describe('Silences', () => { afterEach(() => jest.resetAllMocks());
beforeAll(resetMocks);
afterEach(resetMocks);
describe('Silences', () => {
it( it(
'loads and shows silences', 'loads and shows silences',
async () => { async () => {
@@ -211,8 +205,6 @@ describe('Silences', () => {
describe('Silence create/edit', () => { describe('Silence create/edit', () => {
const baseUrlPath = '/alerting/silence/new'; const baseUrlPath = '/alerting/silence/new';
beforeAll(resetMocks);
afterEach(resetMocks);
beforeEach(() => { beforeEach(() => {
mockAlertRuleApi(server).getAlertRule(MOCK_SILENCE_ID_EXISTING_ALERT_RULE_UID, grafanaRulerRule); mockAlertRuleApi(server).getAlertRule(MOCK_SILENCE_ID_EXISTING_ALERT_RULE_UID, grafanaRulerRule);

View File

@@ -5,14 +5,20 @@ import { Provider } from 'react-redux';
import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { setAlertmanagerChoices } from 'app/features/alerting/unified/mocks/server/configure'; import { setAlertmanagerChoices } from 'app/features/alerting/unified/mocks/server/configure';
import { configureStore } from 'app/store/configureStore'; import { configureStore } from 'app/store/configureStore';
import { AccessControlAction } from 'app/types/accessControl';
import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/types'; import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/types';
import { grantUserPermissions } from '../mocks';
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
import { GrafanaAlertmanagerDeliveryWarning } from './GrafanaAlertmanagerDeliveryWarning'; import { GrafanaAlertmanagerDeliveryWarning } from './GrafanaAlertmanagerDeliveryWarning';
setupMswServer(); setupMswServer();
describe('GrafanaAlertmanagerDeliveryWarning', () => { describe('GrafanaAlertmanagerDeliveryWarning', () => {
beforeEach(() => {
grantUserPermissions([AccessControlAction.AlertingNotificationsRead]);
});
it('Should not render when the datasource is not Grafana', () => { it('Should not render when the datasource is not Grafana', () => {
setAlertmanagerChoices(AlertmanagerChoice.External, 0); setAlertmanagerChoices(AlertmanagerChoice.External, 0);

View File

@@ -6,6 +6,7 @@ import { Alert, useStyles2 } from '@grafana/ui/src';
import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/types'; import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/types';
import { alertmanagerApi } from '../api/alertmanagerApi'; import { alertmanagerApi } from '../api/alertmanagerApi';
import { AlertingAction, useAlertingAbility } from '../hooks/useAbilities';
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
interface GrafanaAlertmanagerDeliveryWarningProps { interface GrafanaAlertmanagerDeliveryWarningProps {
@@ -14,12 +15,17 @@ interface GrafanaAlertmanagerDeliveryWarningProps {
export function GrafanaAlertmanagerDeliveryWarning({ currentAlertmanager }: GrafanaAlertmanagerDeliveryWarningProps) { export function GrafanaAlertmanagerDeliveryWarning({ currentAlertmanager }: GrafanaAlertmanagerDeliveryWarningProps) {
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const viewingInternalAM = currentAlertmanager === GRAFANA_RULES_SOURCE_NAME; const externalAlertmanager = currentAlertmanager !== GRAFANA_RULES_SOURCE_NAME;
const [readConfigurationStatusSupported, readConfigurationStatusAllowed] = useAlertingAbility(
AlertingAction.ReadConfigurationStatus
);
const canReadConfigurationStatus = readConfigurationStatusSupported && readConfigurationStatusAllowed;
const { currentData: amChoiceStatus } = alertmanagerApi.endpoints.getGrafanaAlertingConfigurationStatus.useQuery( const { currentData: amChoiceStatus } = alertmanagerApi.endpoints.getGrafanaAlertingConfigurationStatus.useQuery(
undefined, undefined,
{ {
skip: !viewingInternalAM, skip: externalAlertmanager || !canReadConfigurationStatus,
} }
); );
@@ -27,7 +33,7 @@ export function GrafanaAlertmanagerDeliveryWarning({ currentAlertmanager }: Graf
amChoiceStatus?.alertmanagersChoice && amChoiceStatus?.alertmanagersChoice &&
[AlertmanagerChoice.External, AlertmanagerChoice.All].includes(amChoiceStatus?.alertmanagersChoice); [AlertmanagerChoice.External, AlertmanagerChoice.All].includes(amChoiceStatus?.alertmanagersChoice);
if (!interactsWithExternalAMs || !viewingInternalAM) { if (!interactsWithExternalAMs || externalAlertmanager) {
return null; return null;
} }

View File

@@ -29,7 +29,7 @@ export const SilenceDetails = ({ silence }: Props) => {
<div>{duration}</div> <div>{duration}</div>
<div className={styles.title}>Created by</div> <div className={styles.title}>Created by</div>
<div>{createdBy}</div> <div>{createdBy}</div>
{silencedAlerts.length > 0 && ( {Array.isArray(silencedAlerts) && (
<> <>
<div className={styles.title}>Affected alerts</div> <div className={styles.title}>Affected alerts</div>
<SilencedAlertsTable silencedAlerts={silencedAlerts} /> <SilencedAlertsTable silencedAlerts={silencedAlerts} />

View File

@@ -30,6 +30,7 @@ import { MATCHER_ALERT_RULE_UID } from 'app/features/alerting/unified/utils/cons
import { getDatasourceAPIUid, GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; import { getDatasourceAPIUid, GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
import { MatcherOperator, SilenceCreatePayload } from 'app/plugins/datasource/alertmanager/types'; import { MatcherOperator, SilenceCreatePayload } from 'app/plugins/datasource/alertmanager/types';
import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbilities';
import { SilenceFormFields } from '../../types/silence-form'; import { SilenceFormFields } from '../../types/silence-form';
import { matcherFieldToMatcher } from '../../utils/alertmanager'; import { matcherFieldToMatcher } from '../../utils/alertmanager';
import { makeAMLink } from '../../utils/misc'; import { makeAMLink } from '../../utils/misc';
@@ -113,6 +114,11 @@ export const SilencesEditor = ({
onCancel, onCancel,
ruleUid, ruleUid,
}: SilencesEditorProps) => { }: SilencesEditorProps) => {
const [previewAlertsSupported, previewAlertsAllowed] = useAlertmanagerAbility(
AlertmanagerAction.PreviewSilencedInstances
);
const canPreview = previewAlertsSupported && previewAlertsAllowed;
const [createSilence, { isLoading }] = alertSilencesApi.endpoints.createSilence.useMutation(); const [createSilence, { isLoading }] = alertSilencesApi.endpoints.createSilence.useMutation();
const formAPI = useForm({ defaultValues: formValues }); const formAPI = useForm({ defaultValues: formValues });
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
@@ -236,7 +242,9 @@ export const SilencesEditor = ({
/> />
</Field> </Field>
)} )}
{canPreview && (
<SilencedInstancesPreview amSourceName={alertManagerSourceName} matchers={matchers} ruleUid={ruleUid} /> <SilencedInstancesPreview amSourceName={alertManagerSourceName} matchers={matchers} ruleUid={ruleUid} />
)}
</FieldSet> </FieldSet>
<Stack gap={1}> <Stack gap={1}>
{isLoading && ( {isLoading && (

View File

@@ -16,12 +16,12 @@ import {
import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { Trans } from 'app/core/internationalization'; import { Trans } from 'app/core/internationalization';
import { alertSilencesApi } from 'app/features/alerting/unified/api/alertSilencesApi'; import { alertSilencesApi } from 'app/features/alerting/unified/api/alertSilencesApi';
import { alertmanagerApi } from 'app/features/alerting/unified/api/alertmanagerApi';
import { featureDiscoveryApi } from 'app/features/alerting/unified/api/featureDiscoveryApi'; import { featureDiscoveryApi } from 'app/features/alerting/unified/api/featureDiscoveryApi';
import { MATCHER_ALERT_RULE_UID, SILENCES_POLL_INTERVAL_MS } from 'app/features/alerting/unified/utils/constants'; import { MATCHER_ALERT_RULE_UID, SILENCES_POLL_INTERVAL_MS } from 'app/features/alerting/unified/utils/constants';
import { GRAFANA_RULES_SOURCE_NAME, getDatasourceAPIUid } from 'app/features/alerting/unified/utils/datasource'; import { GRAFANA_RULES_SOURCE_NAME, getDatasourceAPIUid } from 'app/features/alerting/unified/utils/datasource';
import { AlertmanagerAlert, Silence, SilenceState } from 'app/plugins/datasource/alertmanager/types'; import { AlertmanagerAlert, Silence, SilenceState } from 'app/plugins/datasource/alertmanager/types';
import { alertmanagerApi } from '../../api/alertmanagerApi';
import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbilities'; import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbilities';
import { parseMatchers } from '../../utils/alertmanager'; import { parseMatchers } from '../../utils/alertmanager';
import { getSilenceFiltersFromUrlParams, makeAMLink, stringifyErrorLike } from '../../utils/misc'; import { getSilenceFiltersFromUrlParams, makeAMLink, stringifyErrorLike } from '../../utils/misc';
@@ -35,7 +35,7 @@ import { SilenceStateTag } from './SilenceStateTag';
import { SilencesFilter } from './SilencesFilter'; import { SilencesFilter } from './SilencesFilter';
export interface SilenceTableItem extends Silence { export interface SilenceTableItem extends Silence {
silencedAlerts: AlertmanagerAlert[]; silencedAlerts: AlertmanagerAlert[] | undefined;
} }
type SilenceTableColumnProps = DynamicTableColumnProps<SilenceTableItem>; type SilenceTableColumnProps = DynamicTableColumnProps<SilenceTableItem>;
@@ -47,10 +47,15 @@ interface Props {
const API_QUERY_OPTIONS = { pollingInterval: SILENCES_POLL_INTERVAL_MS, refetchOnFocus: true }; const API_QUERY_OPTIONS = { pollingInterval: SILENCES_POLL_INTERVAL_MS, refetchOnFocus: true };
const SilencesTable = ({ alertManagerSourceName }: Props) => { const SilencesTable = ({ alertManagerSourceName }: Props) => {
const [previewAlertsSupported, previewAlertsAllowed] = useAlertmanagerAbility(
AlertmanagerAction.PreviewSilencedInstances
);
const canPreview = previewAlertsSupported && previewAlertsAllowed;
const { data: alertManagerAlerts = [], isLoading: amAlertsIsLoading } = const { data: alertManagerAlerts = [], isLoading: amAlertsIsLoading } =
alertmanagerApi.endpoints.getAlertmanagerAlerts.useQuery( alertmanagerApi.endpoints.getAlertmanagerAlerts.useQuery(
{ amSourceName: alertManagerSourceName, filter: { silenced: true, active: true, inhibited: true } }, { amSourceName: alertManagerSourceName, filter: { silenced: true, active: true, inhibited: true } },
API_QUERY_OPTIONS { ...API_QUERY_OPTIONS, skip: !canPreview }
); );
const { const {
@@ -83,26 +88,26 @@ const SilencesTable = ({ alertManagerSourceName }: Props) => {
return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id)); return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id));
}; };
return filteredSilencesNotExpired.map((silence) => { return filteredSilencesNotExpired.map((silence) => {
const silencedAlerts = findSilencedAlerts(silence.id); const silencedAlerts = canPreview ? findSilencedAlerts(silence.id) : undefined;
return { return {
id: silence.id, id: silence.id,
data: { ...silence, silencedAlerts }, data: { ...silence, silencedAlerts },
}; };
}); });
}, [filteredSilencesNotExpired, alertManagerAlerts]); }, [filteredSilencesNotExpired, alertManagerAlerts, canPreview]);
const itemsExpired = useMemo((): SilenceTableItemProps[] => { const itemsExpired = useMemo((): SilenceTableItemProps[] => {
const findSilencedAlerts = (id: string) => { const findSilencedAlerts = (id: string) => {
return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id)); return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id));
}; };
return filteredSilencesExpired.map((silence) => { return filteredSilencesExpired.map((silence) => {
const silencedAlerts = findSilencedAlerts(silence.id); const silencedAlerts = canPreview ? findSilencedAlerts(silence.id) : undefined;
return { return {
id: silence.id, id: silence.id,
data: { ...silence, silencedAlerts }, data: { ...silence, silencedAlerts },
}; };
}); });
}, [filteredSilencesExpired, alertManagerAlerts]); }, [filteredSilencesExpired, alertManagerAlerts, canPreview]);
if (isLoading || amAlertsIsLoading) { if (isLoading || amAlertsIsLoading) {
return <LoadingPlaceholder text="Loading silences..." />; return <LoadingPlaceholder text="Loading silences..." />;
@@ -304,7 +309,7 @@ function useColumns(alertManagerSourceName: string) {
id: 'alerts', id: 'alerts',
label: 'Alerts silenced', label: 'Alerts silenced',
renderCell: function renderSilencedAlerts({ data: { silencedAlerts } }) { renderCell: function renderSilencedAlerts({ data: { silencedAlerts } }) {
return <span data-testid="alerts">{silencedAlerts.length}</span>; return <span data-testid="alerts">{Array.isArray(silencedAlerts) ? silencedAlerts.length : '-'}</span>;
}, },
size: 2, size: 2,
}, },

View File

@@ -136,6 +136,10 @@ exports[`alertmanager abilities should report Create / Update / Delete actions a
false, false,
false, false,
], ],
"preview-silenced-alerts": [
true,
false,
],
"update-external-configuration": [ "update-external-configuration": [
false, false,
false, false,
@@ -245,6 +249,10 @@ exports[`alertmanager abilities should report everything except exporting for Mi
false, false,
true, true,
], ],
"preview-silenced-alerts": [
true,
true,
],
"update-external-configuration": [ "update-external-configuration": [
true, true,
true, true,
@@ -354,6 +362,10 @@ exports[`alertmanager abilities should report everything is supported for builti
true, true,
true, true,
], ],
"preview-silenced-alerts": [
true,
true,
],
"update-external-configuration": [ "update-external-configuration": [
true, true,
false, false,

View File

@@ -53,6 +53,7 @@ export enum AlertmanagerAction {
CreateSilence = 'create-silence', CreateSilence = 'create-silence',
ViewSilence = 'view-silence', ViewSilence = 'view-silence',
UpdateSilence = 'update-silence', UpdateSilence = 'update-silence',
PreviewSilencedInstances = 'preview-silenced-alerts',
// mute timings // mute timings
ViewMuteTiming = 'view-mute-timing', ViewMuteTiming = 'view-mute-timing',
@@ -83,6 +84,7 @@ export enum AlertingAction {
UpdateAlertRule = 'update-alert-rule', UpdateAlertRule = 'update-alert-rule',
DeleteAlertRule = 'delete-alert-rule', DeleteAlertRule = 'delete-alert-rule',
ExportGrafanaManagedRules = 'export-grafana-managed-rules', ExportGrafanaManagedRules = 'export-grafana-managed-rules',
ReadConfigurationStatus = 'read-configuration-status',
// external (any compatible alerting data source) // external (any compatible alerting data source)
CreateExternalAlertRule = 'create-external-alert-rule', CreateExternalAlertRule = 'create-external-alert-rule',
@@ -110,6 +112,11 @@ export const useAlertingAbilities = (): Abilities<AlertingAction> => {
[AlertingAction.UpdateAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleUpdate), [AlertingAction.UpdateAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleUpdate),
[AlertingAction.DeleteAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleDelete), [AlertingAction.DeleteAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleDelete),
[AlertingAction.ExportGrafanaManagedRules]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleRead), [AlertingAction.ExportGrafanaManagedRules]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleRead),
[AlertingAction.ReadConfigurationStatus]: [
AlwaysSupported,
ctx.hasPermission(AccessControlAction.AlertingInstanceRead) ||
ctx.hasPermission(AccessControlAction.AlertingNotificationsRead),
],
// external // external
[AlertingAction.CreateExternalAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleExternalWrite), [AlertingAction.CreateExternalAlertRule]: toAbility(AlwaysSupported, AccessControlAction.AlertingRuleExternalWrite),
@@ -241,6 +248,7 @@ export function useAllAlertmanagerAbilities(): Abilities<AlertmanagerAction> {
[AlertmanagerAction.CreateSilence]: toAbility(AlwaysSupported, instancePermissions.create), [AlertmanagerAction.CreateSilence]: toAbility(AlwaysSupported, instancePermissions.create),
[AlertmanagerAction.ViewSilence]: toAbility(AlwaysSupported, instancePermissions.read), [AlertmanagerAction.ViewSilence]: toAbility(AlwaysSupported, instancePermissions.read),
[AlertmanagerAction.UpdateSilence]: toAbility(AlwaysSupported, instancePermissions.update), [AlertmanagerAction.UpdateSilence]: toAbility(AlwaysSupported, instancePermissions.update),
[AlertmanagerAction.PreviewSilencedInstances]: toAbility(AlwaysSupported, instancePermissions.read),
// -- mute timtings -- // -- mute timtings --
[AlertmanagerAction.CreateMuteTiming]: toAbility(hasConfigurationAPI, notificationsPermissions.create), [AlertmanagerAction.CreateMuteTiming]: toAbility(hasConfigurationAPI, notificationsPermissions.create),
[AlertmanagerAction.ViewMuteTiming]: toAbility(AlwaysSupported, notificationsPermissions.read), [AlertmanagerAction.ViewMuteTiming]: toAbility(AlwaysSupported, notificationsPermissions.read),

View File

@@ -48,6 +48,21 @@ export const notificationsPermissions = {
}, },
}; };
export const silencesPermissions = {
read: {
grafana: AccessControlAction.AlertingSilenceRead,
external: AccessControlAction.AlertingInstanceRead,
},
create: {
grafana: AccessControlAction.AlertingSilenceCreate,
external: AccessControlAction.AlertingInstancesExternalWrite,
},
update: {
grafana: AccessControlAction.AlertingSilenceUpdate,
external: AccessControlAction.AlertingInstancesExternalWrite,
},
};
export const provisioningPermissions = { export const provisioningPermissions = {
read: AccessControlAction.AlertingProvisioningRead, read: AccessControlAction.AlertingProvisioningRead,
readSecrets: AccessControlAction.AlertingProvisioningReadSecrets, readSecrets: AccessControlAction.AlertingProvisioningReadSecrets,

View File

@@ -14,7 +14,7 @@ import { alertmanagerApi } from '../api/alertmanagerApi';
import { useAlertManagersByPermission } from '../hooks/useAlertManagerSources'; import { useAlertManagersByPermission } from '../hooks/useAlertManagerSources';
import { isAlertManagerWithConfigAPI } from '../state/AlertmanagerContext'; import { isAlertManagerWithConfigAPI } from '../state/AlertmanagerContext';
import { instancesPermissions, notificationsPermissions } from './access-control'; import { instancesPermissions, notificationsPermissions, silencesPermissions } from './access-control';
import { getAllDataSources } from './config'; import { getAllDataSources } from './config';
export const GRAFANA_RULES_SOURCE_NAME = 'grafana'; export const GRAFANA_RULES_SOURCE_NAME = 'grafana';
@@ -144,9 +144,15 @@ export function getAlertManagerDataSourcesByPermission(permission: 'instance' |
const permissions = { const permissions = {
instance: instancesPermissions.read, instance: instancesPermissions.read,
notification: notificationsPermissions.read, notification: notificationsPermissions.read,
silence: silencesPermissions.read,
}; };
if (contextSrv.hasPermission(permissions[permission].grafana)) { const builtinAlertmanagerPermissions = Object.values(permissions).flatMap((permissions) => permissions.grafana);
const hasPermissionsForInternalAlertmanager = builtinAlertmanagerPermissions.some((permission) =>
contextSrv.hasPermission(permission)
);
if (hasPermissionsForInternalAlertmanager) {
availableInternalDataSources.push(grafanaAlertManagerDataSource); availableInternalDataSources.push(grafanaAlertManagerDataSource);
} }