mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Export contact points to check access control action instead legacy role (#71990)
* introduce a new action "alert.provisioning.secrets:read" and role "fixed:alerting.provisioning.secrets:reader" * update alerting API authorization layer to let the user read provisioning with the new action * let new action use decrypt flag * add action and role to docs
This commit is contained in:
parent
e1d239a86e
commit
6b4a9d73d7
@ -47,6 +47,7 @@ The following list contains role-based access control actions.
|
||||
| `alert.rules:read` | `folders:*`<br>`folders:uid:*` | Read Grafana alert rules in a folder and its subfolders. Combine this permission with `folders:read` in a scope that includes the folder and `datasources:query` in the scope of data sources the user can query. |
|
||||
| `alert.rules:write` | `folders:*`<br>`folders:uid:*` | Update Grafana alert rules in a folder and its subfolders. Combine this permission with `folders:read` in a scope that includes the folder and `datasources:query` in the scope of data sources the user can query. |
|
||||
| `alert.provisioning:read` | n/a | Read all Grafana alert rules, notification policies, etc via provisioning API. Permissions to folders and datasource are not required. |
|
||||
| `alert.provisioning.secrets:read` | n/a | Same as `alert.provisioning:read` plus ability to export resources with decrypted secrets. |
|
||||
| `alert.provisioning:write` | n/a | Update all Grafana alert rules, notification policies, etc via provisioning API. Permissions to folders and datasource are not required. |
|
||||
| `annotations:create` | `annotations:*`<br>`annotations:type:*` | Create annotations. |
|
||||
| `annotations:delete` | `annotations:*`<br>`annotations:type:*` | Delete annotations. |
|
||||
|
@ -23,81 +23,82 @@ The following tables list permissions associated with basic and fixed roles.
|
||||
|
||||
## Basic role assignments
|
||||
|
||||
| Basic role | Associated fixed roles | Description |
|
||||
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| Grafana Admin | `fixed:roles:reader`<br>`fixed:roles:writer`<br>`fixed:users:reader`<br>`fixed:users:writer`<br>`fixed:org.users:reader`<br>`fixed:org.users:writer`<br>`fixed:ldap:reader`<br>`fixed:ldap:writer`<br>`fixed:stats:reader`<br>`fixed:settings:reader`<br>`fixed:settings:writer`<br>`fixed:provisioning:writer`<br>`fixed:organization:reader`<br>`fixed:organization:maintainer`<br>`fixed:licensing:reader`<br>`fixed:licensing:writer`<br>`fixed:datasources.caching:reader`<br>`fixed:datasources.caching:writer`<br>`fixed:dashboards.insights:reader`<br>`fixed:datasources.insights:reader`<br>`fixed:plugins:maintainer`<br>`fixed:authentication.config:writer` | Default [Grafana server administrator]({{< relref "../#grafana-server-administrators" >}}) assignments. |
|
||||
| Admin | `fixed:reports:reader`<br>`fixed:reports:writer`<br>`fixed:datasources:reader`<br>`fixed:datasources:writer`<br>`fixed:organization:writer`<br>`fixed:datasources.permissions:reader`<br>`fixed:datasources.permissions:writer`<br>`fixed:teams:writer`<br>`fixed:dashboards:reader`<br>`fixed:dashboards:writer`<br>`fixed:dashboards.permissions:reader`<br>`fixed:dashboards.permissions:writer`<br>`fixed:dashboards.public:writer`<br>`fixed:folders:reader`<br>`fixed:folders:writer`<br>`fixed:folders.permissions:reader`<br>`fixed:folders.permissions:writer`<br>`fixed:alerting:writer`<br>`fixed:apikeys:reader`<br>`fixed:apikeys:writer`<br>`fixed:alerting.provisioning:writer`<br>`fixed:datasources.caching:reader`<br>`fixed:datasources.caching:writer`<br>`fixed:dashboards.insights:reader`<br>`fixed:datasources.insights:reader`<br>`fixed:plugins:writer` | Default [Grafana organization administrator]({{< relref "../#organization-users-and-permissions" >}}) assignments. |
|
||||
| Editor | `fixed:datasources:explorer`<br>`fixed:dashboards:creator`<br>`fixed:folders:creator`<br>`fixed:annotations:writer`<br>`fixed:teams:creator` if the `editors_can_admin` configuration flag is enabled<br>`fixed:alerting:writer`<br>`fixed:dashboards.insights:reader`<br>`fixed:datasources.insights:reader` | Default [Editor]({{< relref "../#organization-users-and-permissions" >}}) assignments. |
|
||||
| Viewer | `fixed:datasources:id:reader`<br>`fixed:organization:reader`<br>`fixed:annotations:reader`<br>`fixed:annotations.dashboard:writer`<br>`fixed:alerting:reader`<br>`fixed:plugins.app:reader`<br>`fixed:dashboards.insights:reader`<br>`fixed:datasources.insights:reader` | Default [Viewer]({{< relref "../#organization-users-and-permissions" >}}) assignments. |
|
||||
| Basic role | Associated fixed roles | Description |
|
||||
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| Grafana Admin | `fixed:roles:reader`<br>`fixed:roles:writer`<br>`fixed:users:reader`<br>`fixed:users:writer`<br>`fixed:org.users:reader`<br>`fixed:org.users:writer`<br>`fixed:ldap:reader`<br>`fixed:ldap:writer`<br>`fixed:stats:reader`<br>`fixed:settings:reader`<br>`fixed:settings:writer`<br>`fixed:provisioning:writer`<br>`fixed:organization:reader`<br>`fixed:organization:maintainer`<br>`fixed:licensing:reader`<br>`fixed:licensing:writer`<br>`fixed:datasources.caching:reader`<br>`fixed:datasources.caching:writer`<br>`fixed:dashboards.insights:reader`<br>`fixed:datasources.insights:reader`<br>`fixed:plugins:maintainer`<br>`fixed:authentication.config:writer` | Default [Grafana server administrator]({{< relref "../#grafana-server-administrators" >}}) assignments. |
|
||||
| Admin | `fixed:reports:reader`<br>`fixed:reports:writer`<br>`fixed:datasources:reader`<br>`fixed:datasources:writer`<br>`fixed:organization:writer`<br>`fixed:datasources.permissions:reader`<br>`fixed:datasources.permissions:writer`<br>`fixed:teams:writer`<br>`fixed:dashboards:reader`<br>`fixed:dashboards:writer`<br>`fixed:dashboards.permissions:reader`<br>`fixed:dashboards.permissions:writer`<br>`fixed:dashboards.public:writer`<br>`fixed:folders:reader`<br>`fixed:folders:writer`<br>`fixed:folders.permissions:reader`<br>`fixed:folders.permissions:writer`<br>`fixed:alerting:writer`<br>`fixed:apikeys:reader`<br>`fixed:apikeys:writer`<br>`fixed:alerting.provisioning.secrets:reader`<br>`fixed:alerting.provisioning:writer`<br>`fixed:datasources.caching:reader`<br>`fixed:datasources.caching:writer`<br>`fixed:dashboards.insights:reader`<br>`fixed:datasources.insights:reader`<br>`fixed:plugins:writer` | Default [Grafana organization administrator]({{< relref "../#organization-users-and-permissions" >}}) assignments. |
|
||||
| Editor | `fixed:datasources:explorer`<br>`fixed:dashboards:creator`<br>`fixed:folders:creator`<br>`fixed:annotations:writer`<br>`fixed:teams:creator` if the `editors_can_admin` configuration flag is enabled<br>`fixed:alerting:writer`<br>`fixed:dashboards.insights:reader`<br>`fixed:datasources.insights:reader` | Default [Editor]({{< relref "../#organization-users-and-permissions" >}}) assignments. |
|
||||
| Viewer | `fixed:datasources:id:reader`<br>`fixed:organization:reader`<br>`fixed:annotations:reader`<br>`fixed:annotations.dashboard:writer`<br>`fixed:alerting:reader`<br>`fixed:plugins.app:reader`<br>`fixed:dashboards.insights:reader`<br>`fixed:datasources.insights:reader` | Default [Viewer]({{< relref "../#organization-users-and-permissions" >}}) assignments. |
|
||||
|
||||
## Fixed role definitions
|
||||
|
||||
| Fixed role | Permissions | Description |
|
||||
| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `fixed:alerting.instances:writer` | All permissions from `fixed:alerting.instances:reader` and<br> `alert.instances:create`<br>`alert.instances:write` for organization scope <br> `alert.instances.external:write` for scope `datasources:*` | Create, update and expire all silences in the organization produced by Grafana, Mimir, and Loki.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.instances:reader` | `alert.instances:read` for organization scope <br> `alert.instances.external:read` for scope `datasources:*` | Read all alerts and silences in the organization produced by Grafana Alerts and Mimir and Loki alerts and silences.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.notifications:writer` | All permissions from `fixed:alerting.notifications:reader` and<br>`alert.notifications:write`for organization scope<br>`alert.notifications.external:read` for scope `datasources:*` | Create, update, and delete contact points, templates, mute timings and notification policies for Grafana and external Alertmanager.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.notifications:reader` | `alert.notifications:read` for organization scope<br>`alert.notifications.external:read` for scope `datasources:*` | Read all Grafana and Alertmanager contact points, templates, and notification policies.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.rules:writer` | All permissions from `fixed:alerting.rules:reader` and <br> `alert.rule:create` <br> `alert.rule:write` <br> `alert.rule:delete` for scope `folders:*` <br> `alert.rules.external:write` for scope `datasources:*` | Create, update, and delete all\* Grafana, Mimir, and Loki alert rules.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.rules:reader` | `alert.rule:read` for scope `folders:*` <br> `alert.rules.external:read` for scope `datasources:*` | Read all\* Grafana, Mimir, and Loki alert rules.[\*](#alerting-roles) |
|
||||
| `fixed:alerting:writer` | All permissions from `fixed:alerting.rules:writer` <br>`fixed:alerting.instances:writer`<br>`fixed:alerting.notifications:writer` | Create, update, and delete Grafana, Mimir, Loki and Alertmanager alert rules\*, silences, contact points, templates, mute timings, and notification policies.[\*](#alerting-roles) |
|
||||
| `fixed:alerting:reader` | All permissions from `fixed:alerting.rules:reader` <br>`fixed:alerting.instances:reader`<br>`fixed:alerting.notifications:reader` | Read-only permissions for all Grafana, Mimir, Loki and Alertmanager alert rules\*, alerts, contact points, and notification policies.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.provisioning:writer` | `alert.provisioning:read` and `alert.provisioning:write` | Create, update and delete Grafana alert rules, notification policies, contact points, templates, etc via provisioning API. [\*](#alerting-roles) |
|
||||
| `fixed:annotations.dashboard:writer` | `annotations:write` <br>`annotations.create`<br> `annotations:delete` for scope `annotations:type:dashboard` | Create, update and delete dashboard annotations and annotation tags. |
|
||||
| `fixed:annotations:reader` | `annotations:read` for scopes `annotations:type:*` | Read all annotations and annotation tags. |
|
||||
| `fixed:annotations:writer` | All permissions from `fixed:annotations:reader` <br>`annotations:write` <br>`annotations.create`<br> `annotations:delete` for scope `annotations:type:*` | Read, create, update and delete all annotations and annotation tags. |
|
||||
| `fixed:apikeys:reader` | `apikeys:read` for scope `apikeys:*` | Read all api keys. |
|
||||
| `fixed:apikeys:writer` | All permissions from `fixed:apikeys:reader` and <br> `apikeys:create` <br> `apikeys:delete` for scope `apikeys:*` | Read, create, delete all api keys. |
|
||||
| `fixed:authentication.config:writer` | `settings:read` for scope `settings:auth.saml:*` <br> `settings:write` for scope `settings:auth.saml:*` | Read and update authentication and SAML settings. |
|
||||
| `fixed:dashboards:creator` | `dashboards:create`<br>`folders:read` | Create dashboards. |
|
||||
| `fixed:dashboards.insights:reader` | `dashboards.insights:read` | Read dashboard insights data and see presence indicators. |
|
||||
| `fixed:dashboards.permissions:reader` | `dashboards.permissions:read` | Read all dashboard permissions. |
|
||||
| `fixed:dashboards.permissions:writer` | All permissions from `fixed:dashboards.permissions:reader` and <br>`dashboards.permissions:write` | Read and update all dashboard permissions. |
|
||||
| `fixed:dashboards.public:writer` | `dashboards.public:write` | Create, update, delete or pause a public dashboard. |
|
||||
| `fixed:dashboards:reader` | `dashboards:read` | Read all dashboards. |
|
||||
| `fixed:dashboards:writer` | All permissions from `fixed:dashboards:reader` and <br>`dashboards:write`<br>`dashboards:edit`<br>`dashboards:delete`<br>`dashboards:create`<br>`dashboards.permissions:read`<br>`dashboards.permissions:write` | Read, create, update, and delete all dashboards. |
|
||||
| `fixed:datasources.caching:reader` | `datasources.caching:read` | Read data source query caching settings. |
|
||||
| `fixed:datasources.caching:writer` | `datasources.caching:read`<br>`datasources.caching:write` | Enable, disable, or update query caching settings. |
|
||||
| `fixed:datasources:explorer` | `datasources:explore` | Enable the Explore feature. Data source permissions still apply, you can only query data sources for which you have query permissions. |
|
||||
| `fixed:datasources:id:reader` | `datasources.id:read` | Read the ID of a data source based on its name. |
|
||||
| `fixed:datasources.insights:reader` | `datasources.insights:read` | Read data source insights data. |
|
||||
| `fixed:datasources.permissions:reader` | `datasources.permissions:read` | Read data source permissions. |
|
||||
| `fixed:datasources.permissions:writer` | All permissions from `fixed:datasources.permissions:reader` and <br>`datasources.permissions:write` | Create, read, or delete permissions of a data source. |
|
||||
| `fixed:datasources:reader` | `datasources:read`<br>`datasources:query` | Read and query data sources. |
|
||||
| `fixed:datasources:writer` | All permissions from `fixed:datasources:reader` and <br>`datasources:create`<br>`datasources:write`<br>`datasources:delete` | Read, query, create, delete, or update a data source. |
|
||||
| `fixed:folders.permissions:reader` | `folders.permissions:read` | Read all folder permissions. |
|
||||
| `fixed:folders.permissions:writer` | All permissions from `fixed:folders.permissions:reader` and <br>`folders.permissions:write` | Read and update all folder permissions. |
|
||||
| `fixed:folders:creator` | `folders:create` | Create folders in the root level. If granted together with `folders:write` permission, also allows creating subfolders under all folders. |
|
||||
| `fixed:folders:reader` | `folders:read`<br>`dashboards:read` | Read all folders and dashboards. |
|
||||
| `fixed:folders:writer` | All permissions from `fixed:dashboards:writer` and <br>`folders:read`<br>`folders:write`<br>`folders:create`<br>`folders:delete`<br>`folders.permissions:read`<br>`folders.permissions:write` | Read, create, update, and delete all folders and dashboards. If granted together with `fixed:folders:creator`, allows creating subfolders under all folders. |
|
||||
| `fixed:ldap:reader` | `ldap.user:read`<br>`ldap.status:read` | Read the LDAP configuration and LDAP status information. |
|
||||
| `fixed:ldap:writer` | All permissions from `fixed:ldap:reader` and <br>`ldap.user:sync`<br>`ldap.config:reload` | Read and update the LDAP configuration, and read LDAP status information. |
|
||||
| `fixed:licensing:reader` | `licensing:read`<br>`licensing.reports:read` | Read licensing information and licensing reports. |
|
||||
| `fixed:licensing:writer` | All permissions from `fixed:licensing:viewer` and <br>`licensing:write`<br>`licensing:delete` | Read licensing information and licensing reports, update and delete the license token. |
|
||||
| `fixed:org.users:reader` | `org.users:read` | Read users within a single organization. |
|
||||
| `fixed:org.users:writer` | All permissions from `fixed:org.users:reader` and <br>`org.users:add`<br>`org.users:remove`<br>`org.users:write` | Within a single organization, add a user, invite a new user, read information about a user and their role, remove a user from that organization, or change the role of a user. |
|
||||
| `fixed:organization:maintainer` | All permissions from `fixed:organization:reader` and <br> `orgs:write`<br>`orgs:create`<br>`orgs:delete`<br>`orgs.quotas:write` | Create, read, write, or delete an organization. Read or write its quotas. This role needs to be assigned globally. |
|
||||
| `fixed:organization:reader` | `orgs:read`<br>`orgs.quotas:read` | Read an organization and its quotas. |
|
||||
| `fixed:organization:writer` | All permissions from `fixed:organization:reader` and <br> `orgs:write`<br>`orgs.preferences:read`<br>`orgs.preferences:write` | Read an organization, its quotas, or its preferences. Update organization properties, or its preferences. |
|
||||
| `fixed:plugins.app:reader` | `plugins.app:access` | Access application plugins (still enforcing the organization role). |
|
||||
| `fixed:plugins:maintainer` | `plugins:install` | Install and uninstall plugins. |
|
||||
| `fixed:plugins:writer` | `plugins:write` | Enable and disable plugins and edit plugins' settings. |
|
||||
| `fixed:provisioning:writer` | `provisioning:reload` | Reload provisioning. |
|
||||
| `fixed:reports:reader` | `reports:read`<br>`reports:send`<br>`reports.settings:read` | Read all reports and shared report settings. |
|
||||
| `fixed:reports:writer` | All permissions from `fixed:reports:reader` and <br>`reports:create`<br>`reports:write`<br>`reports:delete`<br>`reports.settings:write` | Create, read, update, or delete all reports and shared report settings. |
|
||||
| `fixed:roles:reader` | `roles:read`<br>`teams.roles:read`<br>`users.roles:read`<br>`users.permissions:read` | Read all access control roles, roles and permissions assigned to users, teams. |
|
||||
| `fixed:roles:writer` | All permissions from `fixed:roles:reader` and <br>`roles:write`<br>`roles:delete`<br>`teams.roles:add`<br>`teams.roles:remove`<br>`users.roles:add`<br>`users.roles:remove` | Create, read, update, or delete all roles, assign or unassign roles to users, teams. |
|
||||
| `fixed:roles:resetter` | `roles:write` with scope `permissions:type:escalate` | Reset basic roles to their default. |
|
||||
| `fixed:serviceaccounts:reader` | `serviceaccounts:read` | Read Grafana service accounts. |
|
||||
| `fixed:serviceaccounts:creator` | `serviceaccounts:create` | Create Grafana service accounts. |
|
||||
| `fixed:serviceaccounts:writer` | `serviceaccounts:read`<br>`serviceaccounts:create`<br>`serviceaccounts:write`<br>`serviceaccounts:delete`<br>`serviceaccounts.permissions:read`<br>`serviceaccounts.permissions:write` | Create, update, read and delete all Grafana service accounts and manage service account permissions. |
|
||||
| `fixed:settings:reader` | `settings:read` | Read Grafana instance settings. |
|
||||
| `fixed:settings:writer` | All permissions from `fixed:settings:reader` and<br>`settings:write` | Read and update Grafana instance settings. |
|
||||
| `fixed:stats:reader` | `server.stats:read` | Read Grafana instance statistics. |
|
||||
| `fixed:teams:creator` | `teams:create`<br>`org.users:read` | Create a team and list organization users (required to manage the created team). |
|
||||
| `fixed:teams:writer` | `teams:create`<br>`teams:delete`<br>`teams:read`<br>`teams:write`<br>`teams.permissions:read`<br>`teams.permissions:write` | Create, read, update and delete teams and manage team memberships. |
|
||||
| `fixed:users:reader` | `users:read`<br>`users.quotas:read`<br>`users.authtoken:read`<br>` | Read all users and their information, such as team memberships, authentication tokens, and quotas. |
|
||||
| `fixed:users:writer` | All permissions from `fixed:users:reader` and <br>`users:write`<br>`users:create`<br>`users:delete`<br>`users:enable`<br>`users:disable`<br>`users.password:write`<br>`users.permissions:write`<br>`users:logout`<br>`users.authtoken:write`<br>`users.quotas:write` | Read and update all attributes and settings for all users in Grafana: update user information, read user information, create or enable or disable a user, make a user a Grafana administrator, sign out a user, update a user’s authentication token, or update quotas for all users. |
|
||||
| Fixed role | Permissions | Description |
|
||||
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `fixed:alerting.instances:writer` | All permissions from `fixed:alerting.instances:reader` and<br> `alert.instances:create`<br>`alert.instances:write` for organization scope <br> `alert.instances.external:write` for scope `datasources:*` | Create, update and expire all silences in the organization produced by Grafana, Mimir, and Loki.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.instances:reader` | `alert.instances:read` for organization scope <br> `alert.instances.external:read` for scope `datasources:*` | Read all alerts and silences in the organization produced by Grafana Alerts and Mimir and Loki alerts and silences.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.notifications:writer` | All permissions from `fixed:alerting.notifications:reader` and<br>`alert.notifications:write`for organization scope<br>`alert.notifications.external:read` for scope `datasources:*` | Create, update, and delete contact points, templates, mute timings and notification policies for Grafana and external Alertmanager.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.notifications:reader` | `alert.notifications:read` for organization scope<br>`alert.notifications.external:read` for scope `datasources:*` | Read all Grafana and Alertmanager contact points, templates, and notification policies.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.rules:writer` | All permissions from `fixed:alerting.rules:reader` and <br> `alert.rule:create` <br> `alert.rule:write` <br> `alert.rule:delete` for scope `folders:*` <br> `alert.rules.external:write` for scope `datasources:*` | Create, update, and delete all\* Grafana, Mimir, and Loki alert rules.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.rules:reader` | `alert.rule:read` for scope `folders:*` <br> `alert.rules.external:read` for scope `datasources:*` | Read all\* Grafana, Mimir, and Loki alert rules.[\*](#alerting-roles) |
|
||||
| `fixed:alerting:writer` | All permissions from `fixed:alerting.rules:writer` <br>`fixed:alerting.instances:writer`<br>`fixed:alerting.notifications:writer` | Create, update, and delete Grafana, Mimir, Loki and Alertmanager alert rules\*, silences, contact points, templates, mute timings, and notification policies.[\*](#alerting-roles) |
|
||||
| `fixed:alerting:reader` | All permissions from `fixed:alerting.rules:reader` <br>`fixed:alerting.instances:reader`<br>`fixed:alerting.notifications:reader` | Read-only permissions for all Grafana, Mimir, Loki and Alertmanager alert rules\*, alerts, contact points, and notification policies.[\*](#alerting-roles) |
|
||||
| `fixed:alerting.provisioning.secrets:reader` | `alert.provisioning:read` and `alert.provisioning.secrets:read` | Read-only permissions for Provisioning API and let export resources with decrypted secrets [\*](#alerting-roles) |
|
||||
| `fixed:alerting.provisioning:writer` | `alert.provisioning:read` and `alert.provisioning:write` | Create, update and delete Grafana alert rules, notification policies, contact points, templates, etc via provisioning API. [\*](#alerting-roles) |
|
||||
| `fixed:annotations.dashboard:writer` | `annotations:write` <br>`annotations.create`<br> `annotations:delete` for scope `annotations:type:dashboard` | Create, update and delete dashboard annotations and annotation tags. |
|
||||
| `fixed:annotations:reader` | `annotations:read` for scopes `annotations:type:*` | Read all annotations and annotation tags. |
|
||||
| `fixed:annotations:writer` | All permissions from `fixed:annotations:reader` <br>`annotations:write` <br>`annotations.create`<br> `annotations:delete` for scope `annotations:type:*` | Read, create, update and delete all annotations and annotation tags. |
|
||||
| `fixed:apikeys:reader` | `apikeys:read` for scope `apikeys:*` | Read all api keys. |
|
||||
| `fixed:apikeys:writer` | All permissions from `fixed:apikeys:reader` and <br> `apikeys:create` <br> `apikeys:delete` for scope `apikeys:*` | Read, create, delete all api keys. |
|
||||
| `fixed:authentication.config:writer` | `settings:read` for scope `settings:auth.saml:*` <br> `settings:write` for scope `settings:auth.saml:*` | Read and update authentication and SAML settings. |
|
||||
| `fixed:dashboards:creator` | `dashboards:create`<br>`folders:read` | Create dashboards. |
|
||||
| `fixed:dashboards.insights:reader` | `dashboards.insights:read` | Read dashboard insights data and see presence indicators. |
|
||||
| `fixed:dashboards.permissions:reader` | `dashboards.permissions:read` | Read all dashboard permissions. |
|
||||
| `fixed:dashboards.permissions:writer` | All permissions from `fixed:dashboards.permissions:reader` and <br>`dashboards.permissions:write` | Read and update all dashboard permissions. |
|
||||
| `fixed:dashboards.public:writer` | `dashboards.public:write` | Create, update, delete or pause a public dashboard. |
|
||||
| `fixed:dashboards:reader` | `dashboards:read` | Read all dashboards. |
|
||||
| `fixed:dashboards:writer` | All permissions from `fixed:dashboards:reader` and <br>`dashboards:write`<br>`dashboards:edit`<br>`dashboards:delete`<br>`dashboards:create`<br>`dashboards.permissions:read`<br>`dashboards.permissions:write` | Read, create, update, and delete all dashboards. |
|
||||
| `fixed:datasources.caching:reader` | `datasources.caching:read` | Read data source query caching settings. |
|
||||
| `fixed:datasources.caching:writer` | `datasources.caching:read`<br>`datasources.caching:write` | Enable, disable, or update query caching settings. |
|
||||
| `fixed:datasources:explorer` | `datasources:explore` | Enable the Explore feature. Data source permissions still apply, you can only query data sources for which you have query permissions. |
|
||||
| `fixed:datasources:id:reader` | `datasources.id:read` | Read the ID of a data source based on its name. |
|
||||
| `fixed:datasources.insights:reader` | `datasources.insights:read` | Read data source insights data. |
|
||||
| `fixed:datasources.permissions:reader` | `datasources.permissions:read` | Read data source permissions. |
|
||||
| `fixed:datasources.permissions:writer` | All permissions from `fixed:datasources.permissions:reader` and <br>`datasources.permissions:write` | Create, read, or delete permissions of a data source. |
|
||||
| `fixed:datasources:reader` | `datasources:read`<br>`datasources:query` | Read and query data sources. |
|
||||
| `fixed:datasources:writer` | All permissions from `fixed:datasources:reader` and <br>`datasources:create`<br>`datasources:write`<br>`datasources:delete` | Read, query, create, delete, or update a data source. |
|
||||
| `fixed:folders.permissions:reader` | `folders.permissions:read` | Read all folder permissions. |
|
||||
| `fixed:folders.permissions:writer` | All permissions from `fixed:folders.permissions:reader` and <br>`folders.permissions:write` | Read and update all folder permissions. |
|
||||
| `fixed:folders:creator` | `folders:create` | Create folders in the root level. If granted together with `folders:write` permission, also allows creating subfolders under all folders. |
|
||||
| `fixed:folders:reader` | `folders:read`<br>`dashboards:read` | Read all folders and dashboards. |
|
||||
| `fixed:folders:writer` | All permissions from `fixed:dashboards:writer` and <br>`folders:read`<br>`folders:write`<br>`folders:create`<br>`folders:delete`<br>`folders.permissions:read`<br>`folders.permissions:write` | Read, create, update, and delete all folders and dashboards. If granted together with `fixed:folders:creator`, allows creating subfolders under all folders. |
|
||||
| `fixed:ldap:reader` | `ldap.user:read`<br>`ldap.status:read` | Read the LDAP configuration and LDAP status information. |
|
||||
| `fixed:ldap:writer` | All permissions from `fixed:ldap:reader` and <br>`ldap.user:sync`<br>`ldap.config:reload` | Read and update the LDAP configuration, and read LDAP status information. |
|
||||
| `fixed:licensing:reader` | `licensing:read`<br>`licensing.reports:read` | Read licensing information and licensing reports. |
|
||||
| `fixed:licensing:writer` | All permissions from `fixed:licensing:viewer` and <br>`licensing:write`<br>`licensing:delete` | Read licensing information and licensing reports, update and delete the license token. |
|
||||
| `fixed:org.users:reader` | `org.users:read` | Read users within a single organization. |
|
||||
| `fixed:org.users:writer` | All permissions from `fixed:org.users:reader` and <br>`org.users:add`<br>`org.users:remove`<br>`org.users:write` | Within a single organization, add a user, invite a new user, read information about a user and their role, remove a user from that organization, or change the role of a user. |
|
||||
| `fixed:organization:maintainer` | All permissions from `fixed:organization:reader` and <br> `orgs:write`<br>`orgs:create`<br>`orgs:delete`<br>`orgs.quotas:write` | Create, read, write, or delete an organization. Read or write its quotas. This role needs to be assigned globally. |
|
||||
| `fixed:organization:reader` | `orgs:read`<br>`orgs.quotas:read` | Read an organization and its quotas. |
|
||||
| `fixed:organization:writer` | All permissions from `fixed:organization:reader` and <br> `orgs:write`<br>`orgs.preferences:read`<br>`orgs.preferences:write` | Read an organization, its quotas, or its preferences. Update organization properties, or its preferences. |
|
||||
| `fixed:plugins.app:reader` | `plugins.app:access` | Access application plugins (still enforcing the organization role). |
|
||||
| `fixed:plugins:maintainer` | `plugins:install` | Install and uninstall plugins. |
|
||||
| `fixed:plugins:writer` | `plugins:write` | Enable and disable plugins and edit plugins' settings. |
|
||||
| `fixed:provisioning:writer` | `provisioning:reload` | Reload provisioning. |
|
||||
| `fixed:reports:reader` | `reports:read`<br>`reports:send`<br>`reports.settings:read` | Read all reports and shared report settings. |
|
||||
| `fixed:reports:writer` | All permissions from `fixed:reports:reader` and <br>`reports:create`<br>`reports:write`<br>`reports:delete`<br>`reports.settings:write` | Create, read, update, or delete all reports and shared report settings. |
|
||||
| `fixed:roles:reader` | `roles:read`<br>`teams.roles:read`<br>`users.roles:read`<br>`users.permissions:read` | Read all access control roles, roles and permissions assigned to users, teams. |
|
||||
| `fixed:roles:writer` | All permissions from `fixed:roles:reader` and <br>`roles:write`<br>`roles:delete`<br>`teams.roles:add`<br>`teams.roles:remove`<br>`users.roles:add`<br>`users.roles:remove` | Create, read, update, or delete all roles, assign or unassign roles to users, teams. |
|
||||
| `fixed:roles:resetter` | `roles:write` with scope `permissions:type:escalate` | Reset basic roles to their default. |
|
||||
| `fixed:serviceaccounts:reader` | `serviceaccounts:read` | Read Grafana service accounts. |
|
||||
| `fixed:serviceaccounts:creator` | `serviceaccounts:create` | Create Grafana service accounts. |
|
||||
| `fixed:serviceaccounts:writer` | `serviceaccounts:read`<br>`serviceaccounts:create`<br>`serviceaccounts:write`<br>`serviceaccounts:delete`<br>`serviceaccounts.permissions:read`<br>`serviceaccounts.permissions:write` | Create, update, read and delete all Grafana service accounts and manage service account permissions. |
|
||||
| `fixed:settings:reader` | `settings:read` | Read Grafana instance settings. |
|
||||
| `fixed:settings:writer` | All permissions from `fixed:settings:reader` and<br>`settings:write` | Read and update Grafana instance settings. |
|
||||
| `fixed:stats:reader` | `server.stats:read` | Read Grafana instance statistics. |
|
||||
| `fixed:teams:creator` | `teams:create`<br>`org.users:read` | Create a team and list organization users (required to manage the created team). |
|
||||
| `fixed:teams:writer` | `teams:create`<br>`teams:delete`<br>`teams:read`<br>`teams:write`<br>`teams.permissions:read`<br>`teams.permissions:write` | Create, read, update and delete teams and manage team memberships. |
|
||||
| `fixed:users:reader` | `users:read`<br>`users.quotas:read`<br>`users.authtoken:read`<br>` | Read all users and their information, such as team memberships, authentication tokens, and quotas. |
|
||||
| `fixed:users:writer` | All permissions from `fixed:users:reader` and <br>`users:write`<br>`users:create`<br>`users:delete`<br>`users:enable`<br>`users:disable`<br>`users.password:write`<br>`users.permissions:write`<br>`users:logout`<br>`users.authtoken:write`<br>`users.quotas:write` | Read and update all attributes and settings for all users in Grafana: update user information, read user information, create or enable or disable a user, make a user a Grafana administrator, sign out a user, update a user’s authentication token, or update quotas for all users. |
|
||||
|
||||
### Alerting roles
|
||||
|
||||
|
@ -455,8 +455,9 @@ const (
|
||||
ActionAlertingNotificationsExternalRead = "alert.notifications.external:read"
|
||||
|
||||
// Alerting provisioning actions
|
||||
ActionAlertingProvisioningRead = "alert.provisioning:read"
|
||||
ActionAlertingProvisioningWrite = "alert.provisioning:write"
|
||||
ActionAlertingProvisioningRead = "alert.provisioning:read"
|
||||
ActionAlertingProvisioningReadSecrets = "alert.provisioning.secrets:read"
|
||||
ActionAlertingProvisioningWrite = "alert.provisioning:write"
|
||||
|
||||
// Feature Management actions
|
||||
ActionFeatureManagementRead = "featuremgmt.read"
|
||||
|
@ -171,6 +171,24 @@ var (
|
||||
},
|
||||
Grants: []string{string(org.RoleAdmin)},
|
||||
}
|
||||
|
||||
alertingProvisioningReaderWithSecretsRole = accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: accesscontrol.FixedRolePrefix + "alerting.provisioning.secrets:reader",
|
||||
DisplayName: "Read via Provisioning API + Export Secrets",
|
||||
Description: "Read all alert rules, contact points, notification policies, silences, etc. in the organization via provisioning API and use export with decrypted secrets",
|
||||
Group: AlertRolesGroup,
|
||||
Permissions: []accesscontrol.Permission{
|
||||
{
|
||||
Action: accesscontrol.ActionAlertingProvisioningReadSecrets, // organization scope
|
||||
},
|
||||
{
|
||||
Action: accesscontrol.ActionAlertingProvisioningRead, // organization scope
|
||||
},
|
||||
},
|
||||
},
|
||||
Grants: []string{string(org.RoleAdmin)},
|
||||
}
|
||||
)
|
||||
|
||||
func DeclareFixedRoles(service accesscontrol.Service) error {
|
||||
@ -178,6 +196,6 @@ func DeclareFixedRoles(service accesscontrol.Service) error {
|
||||
rulesReaderRole, rulesWriterRole,
|
||||
instancesReaderRole, instancesWriterRole,
|
||||
notificationsReaderRole, notificationsWriterRole,
|
||||
alertingReaderRole, alertingWriterRole, alertingProvisionerRole,
|
||||
alertingReaderRole, alertingWriterRole, alertingProvisionerRole, alertingProvisioningReaderWithSecretsRole,
|
||||
)
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/log/logtest"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
@ -27,7 +29,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
secrets_fakes "github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
@ -1008,8 +1009,15 @@ func TestProvisioningApiContactPointExport(t *testing.T) {
|
||||
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
||||
})
|
||||
|
||||
t.Run("decrypt true without admin returns 403", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
t.Run("decrypt true without alert.provisioning.secrets:read permissions returns 403", func(t *testing.T) {
|
||||
env := createTestEnv(t, testConfig)
|
||||
env.ac = &recordingAccessControlFake{
|
||||
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
return false, nil
|
||||
},
|
||||
}
|
||||
|
||||
sut := createProvisioningSrvSutFromEnv(t, &env)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
rc.Context.Req.Form.Set("decrypt", "true")
|
||||
@ -1017,19 +1025,30 @@ func TestProvisioningApiContactPointExport(t *testing.T) {
|
||||
response := sut.RouteGetContactPointsExport(&rc)
|
||||
|
||||
require.Equal(t, 403, response.Status())
|
||||
require.Len(t, env.ac.EvaluateRecordings, 1)
|
||||
require.Equal(t, accesscontrol.ActionAlertingProvisioningReadSecrets, env.ac.EvaluateRecordings[0].Evaluator.String())
|
||||
})
|
||||
|
||||
t.Run("decrypt true with admin returns 200", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
env := createTestEnv(t, testConfig)
|
||||
env.ac = &recordingAccessControlFake{
|
||||
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
require.Equal(t, accesscontrol.ActionAlertingProvisioningReadSecrets, evaluator.String())
|
||||
return true, nil
|
||||
},
|
||||
}
|
||||
|
||||
sut := createProvisioningSrvSutFromEnv(t, &env)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
rc.SignedInUser.OrgRole = org.RoleAdmin
|
||||
rc.Context.Req.Form.Set("decrypt", "true")
|
||||
|
||||
response := sut.RouteGetContactPointsExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Len(t, env.ac.EvaluateRecordings, 1)
|
||||
require.Equal(t, accesscontrol.ActionAlertingProvisioningReadSecrets, env.ac.EvaluateRecordings[0].Evaluator.String())
|
||||
})
|
||||
|
||||
t.Run("json body content is as expected", func(t *testing.T) {
|
||||
@ -1061,10 +1080,12 @@ func TestProvisioningApiContactPointExport(t *testing.T) {
|
||||
})
|
||||
t.Run("decrypt true", func(t *testing.T) {
|
||||
env := createTestEnv(t, testContactPointConfig)
|
||||
env.ac.Callback = func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
sut := createProvisioningSrvSutFromEnv(t, &env)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
rc.SignedInUser.OrgRole = org.RoleAdmin
|
||||
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||
rc.Context.Req.Form.Set("decrypt", "true")
|
||||
|
||||
@ -1119,10 +1140,12 @@ func TestProvisioningApiContactPointExport(t *testing.T) {
|
||||
})
|
||||
t.Run("decrypt true", func(t *testing.T) {
|
||||
env := createTestEnv(t, testContactPointConfig)
|
||||
env.ac.Callback = func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
sut := createProvisioningSrvSutFromEnv(t, &env)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
rc.SignedInUser.OrgRole = org.RoleAdmin
|
||||
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
||||
rc.Context.Req.Form.Set("decrypt", "true")
|
||||
|
||||
@ -1160,6 +1183,7 @@ type testEnvironment struct {
|
||||
xact provisioning.TransactionManager
|
||||
quotas provisioning.QuotaChecker
|
||||
prov provisioning.ProvisioningStore
|
||||
ac *recordingAccessControlFake
|
||||
}
|
||||
|
||||
func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
||||
@ -1212,6 +1236,8 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
||||
Title: "Folder Title2",
|
||||
}}, nil).Maybe()
|
||||
|
||||
ac := &recordingAccessControlFake{}
|
||||
|
||||
return testEnvironment{
|
||||
secrets: secretsService,
|
||||
log: log,
|
||||
@ -1221,6 +1247,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
||||
xact: xact,
|
||||
prov: prov,
|
||||
quotas: quotas,
|
||||
ac: ac,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1237,7 +1264,7 @@ func createProvisioningSrvSutFromEnv(t *testing.T, env *testEnvironment) Provisi
|
||||
return ProvisioningSrv{
|
||||
log: env.log,
|
||||
policies: newFakeNotificationPolicyService(),
|
||||
contactPointService: provisioning.NewContactPointService(env.configs, env.secrets, env.prov, env.xact, env.log),
|
||||
contactPointService: provisioning.NewContactPointService(env.configs, env.secrets, env.prov, env.xact, env.log, env.ac),
|
||||
templates: provisioning.NewTemplateService(env.configs, env.prov, env.xact, env.log),
|
||||
muteTimings: provisioning.NewMuteTimingService(env.configs, env.prov, env.xact, env.log),
|
||||
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.dashboardService, env.quotas, env.xact, 60, 10, env.log),
|
||||
@ -1256,6 +1283,7 @@ func createTestRequestCtx() contextmodel.ReqContext {
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
},
|
||||
Logger: &logtest.Fake{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,7 +198,7 @@ func (api *API) authorize(method, path string) web.Handler {
|
||||
http.MethodGet + "/api/v1/provisioning/alert-rules/{UID}/export",
|
||||
http.MethodGet + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}",
|
||||
http.MethodGet + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export":
|
||||
eval = ac.EvalPermission(ac.ActionAlertingProvisioningRead) // organization scope
|
||||
eval = ac.EvalAny(ac.EvalPermission(ac.ActionAlertingProvisioningRead), ac.EvalPermission(ac.ActionAlertingProvisioningReadSecrets)) // organization scope
|
||||
|
||||
case http.MethodPut + "/api/v1/provisioning/policies",
|
||||
http.MethodDelete + "/api/v1/provisioning/policies",
|
||||
|
@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
@ -8,8 +9,10 @@ import (
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
type fakeAlertInstanceManager struct {
|
||||
@ -103,3 +106,34 @@ func (f *fakeAlertInstanceManager) GenerateAlertInstances(orgID int64, alertRule
|
||||
f.states[orgID][alertRuleUID] = append(f.states[orgID][alertRuleUID], newState)
|
||||
}
|
||||
}
|
||||
|
||||
type recordingAccessControlFake struct {
|
||||
Disabled bool
|
||||
EvaluateRecordings []struct {
|
||||
User *user.SignedInUser
|
||||
Evaluator accesscontrol.Evaluator
|
||||
}
|
||||
Callback func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error)
|
||||
}
|
||||
|
||||
func (a *recordingAccessControlFake) Evaluate(ctx context.Context, u *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||
a.EvaluateRecordings = append(a.EvaluateRecordings, struct {
|
||||
User *user.SignedInUser
|
||||
Evaluator accesscontrol.Evaluator
|
||||
}{User: u, Evaluator: evaluator})
|
||||
if a.Callback == nil {
|
||||
return false, nil
|
||||
}
|
||||
return a.Callback(u, evaluator)
|
||||
}
|
||||
|
||||
func (a *recordingAccessControlFake) RegisterScopeAttributeResolver(prefix string, resolver accesscontrol.ScopeAttributeResolver) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (a *recordingAccessControlFake) IsDisabled() bool {
|
||||
return a.Disabled
|
||||
}
|
||||
|
||||
var _ accesscontrol.AccessControl = &recordingAccessControlFake{}
|
||||
|
@ -234,7 +234,7 @@ func (ng *AlertNG) init() error {
|
||||
|
||||
// Provisioning
|
||||
policyService := provisioning.NewNotificationPolicyService(ng.store, ng.store, ng.store, ng.Cfg.UnifiedAlerting, ng.Log)
|
||||
contactPointService := provisioning.NewContactPointService(ng.store, ng.SecretsService, ng.store, ng.store, ng.Log)
|
||||
contactPointService := provisioning.NewContactPointService(ng.store, ng.SecretsService, ng.store, ng.store, ng.Log, ng.accesscontrol)
|
||||
templateService := provisioning.NewTemplateService(ng.store, ng.store, ng.store, ng.Log)
|
||||
muteTimingService := provisioning.NewMuteTimingService(ng.store, ng.store, ng.store, ng.Log)
|
||||
alertRuleService := provisioning.NewAlertRuleService(ng.store, ng.store, ng.dashboardService, ng.QuotaService, ng.store,
|
||||
|
@ -13,10 +13,10 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@ -28,16 +28,18 @@ type ContactPointService struct {
|
||||
provenanceStore ProvisioningStore
|
||||
xact TransactionManager
|
||||
log log.Logger
|
||||
ac accesscontrol.AccessControl
|
||||
}
|
||||
|
||||
func NewContactPointService(store AMConfigStore, encryptionService secrets.Service,
|
||||
provenanceStore ProvisioningStore, xact TransactionManager, log log.Logger) *ContactPointService {
|
||||
provenanceStore ProvisioningStore, xact TransactionManager, log log.Logger, ac accesscontrol.AccessControl) *ContactPointService {
|
||||
return &ContactPointService{
|
||||
amStore: store,
|
||||
encryptionService: encryptionService,
|
||||
provenanceStore: provenanceStore,
|
||||
xact: xact,
|
||||
log: log,
|
||||
ac: ac,
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,10 +51,22 @@ type ContactPointQuery struct {
|
||||
Decrypt bool
|
||||
}
|
||||
|
||||
func (ecp *ContactPointService) canDecryptSecrets(ctx context.Context, u *user.SignedInUser) bool {
|
||||
if u == nil {
|
||||
return false
|
||||
}
|
||||
permitted, err := ecp.ac.Evaluate(ctx, u, accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningReadSecrets))
|
||||
if err != nil {
|
||||
ecp.log.Error("Failed to evaluate user permissions", "error", err)
|
||||
permitted = false
|
||||
}
|
||||
return permitted
|
||||
}
|
||||
|
||||
// GetContactPoints returns contact points. If q.Decrypt is true and the user is an OrgAdmin, decrypted secure settings are included instead of redacted ones.
|
||||
func (ecp *ContactPointService) GetContactPoints(ctx context.Context, q ContactPointQuery, u *user.SignedInUser) ([]apimodels.EmbeddedContactPoint, error) {
|
||||
if q.Decrypt && (u == nil || !u.HasRole(org.RoleAdmin)) {
|
||||
return nil, fmt.Errorf("%w: decrypting secure settings requires Org Admin", ErrPermissionDenied)
|
||||
if q.Decrypt && !ecp.canDecryptSecrets(ctx, u) {
|
||||
return nil, fmt.Errorf("%w: user requires Admin role or alert.provisioning.secrets:read permission to view decrypted secure settings", ErrPermissionDenied)
|
||||
}
|
||||
revision, err := getLastConfiguration(ctx, q.OrgID, ecp.amStore)
|
||||
if err != nil {
|
||||
|
@ -12,14 +12,17 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func TestContactPointService(t *testing.T) {
|
||||
@ -243,6 +246,7 @@ func TestContactPointService(t *testing.T) {
|
||||
func TestContactPointServiceDecryptRedact(t *testing.T) {
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := manager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
||||
ac := acimpl.ProvideAccessControl(setting.NewCfg())
|
||||
t.Run("GetContactPoints gets redacted contact points by default", func(t *testing.T) {
|
||||
sut := createContactPointServiceSut(t, secretsService)
|
||||
|
||||
@ -253,20 +257,36 @@ func TestContactPointServiceDecryptRedact(t *testing.T) {
|
||||
require.Equal(t, "slack receiver", cps[0].Name)
|
||||
require.Equal(t, definitions.RedactedValue, cps[0].Settings.Get("url").MustString())
|
||||
})
|
||||
t.Run("GetContactPoints errors when Decrypt = true and user not Org Admin", func(t *testing.T) {
|
||||
t.Run("GetContactPoints errors when Decrypt = true and user does not have permissions", func(t *testing.T) {
|
||||
sut := createContactPointServiceSut(t, secretsService)
|
||||
sut.ac = ac
|
||||
|
||||
q := cpsQuery(1)
|
||||
q.Decrypt = true
|
||||
_, err := sut.GetContactPoints(context.Background(), q, &user.SignedInUser{})
|
||||
require.ErrorIs(t, err, ErrPermissionDenied)
|
||||
})
|
||||
t.Run("GetContactPoints errors when Decrypt = true and user is nil", func(t *testing.T) {
|
||||
sut := createContactPointServiceSut(t, secretsService)
|
||||
sut.ac = ac
|
||||
|
||||
q := cpsQuery(1)
|
||||
q.Decrypt = true
|
||||
_, err := sut.GetContactPoints(context.Background(), q, nil)
|
||||
require.ErrorIs(t, err, ErrPermissionDenied)
|
||||
})
|
||||
t.Run("GetContactPoints gets decrypted contact points when Decrypt = true and user is Org Admin", func(t *testing.T) {
|
||||
|
||||
t.Run("GetContactPoints gets decrypted contact points when Decrypt = true and user has permissions", func(t *testing.T) {
|
||||
sut := createContactPointServiceSut(t, secretsService)
|
||||
sut.ac = ac
|
||||
|
||||
q := cpsQuery(1)
|
||||
q.Decrypt = true
|
||||
cps, err := sut.GetContactPoints(context.Background(), q, &user.SignedInUser{OrgID: 1, OrgRole: org.RoleAdmin})
|
||||
cps, err := sut.GetContactPoints(context.Background(), q, &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
|
||||
1: {
|
||||
accesscontrol.ActionAlertingProvisioningReadSecrets: nil,
|
||||
},
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, cps, 1)
|
||||
@ -325,6 +345,7 @@ func createContactPointServiceSut(t *testing.T, secretService secrets.Service) *
|
||||
xact: newNopTransactionManager(),
|
||||
encryptionService: secretService,
|
||||
log: log.NewNopLogger(),
|
||||
ac: actest.FakeAccessControl{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,7 +276,7 @@ func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error
|
||||
int64(ps.Cfg.UnifiedAlerting.BaseInterval.Seconds()),
|
||||
ps.log)
|
||||
contactPointService := provisioning.NewContactPointService(&st, ps.secretService,
|
||||
st, ps.SQLStore, ps.log)
|
||||
st, ps.SQLStore, ps.log, ps.ac)
|
||||
notificationPolicyService := provisioning.NewNotificationPolicyService(&st,
|
||||
st, ps.SQLStore, ps.Cfg.UnifiedAlerting, ps.log)
|
||||
mutetimingsService := provisioning.NewMuteTimingService(&st, st, &st, ps.log)
|
||||
|
@ -696,6 +696,21 @@ describe('RuleList', () => {
|
||||
await userEvent.click(ui.moreButton.get());
|
||||
expect(ui.exportButton.get()).toBeInTheDocument();
|
||||
});
|
||||
it('Export button should be visible when the user has alert provisioning read secrets permissions', async () => {
|
||||
enableRBAC();
|
||||
|
||||
grantUserPermissions([AccessControlAction.AlertingProvisioningReadSecrets]);
|
||||
|
||||
mocks.getAllDataSourcesMock.mockReturnValue([]);
|
||||
setDataSourceSrv(new MockDataSourceSrv({}));
|
||||
mocks.api.fetchRules.mockResolvedValue([]);
|
||||
mocks.api.fetchRulerRules.mockResolvedValue({});
|
||||
|
||||
renderRuleList();
|
||||
|
||||
await userEvent.click(ui.moreButton.get());
|
||||
expect(ui.exportButton.get()).toBeInTheDocument();
|
||||
});
|
||||
it('Export button should not be visible when the user has no alert provisioning read permissions', async () => {
|
||||
enableRBAC();
|
||||
|
||||
|
@ -74,7 +74,9 @@ const Policy: FC<PolicyComponentProps> = ({
|
||||
const permissions = getNotificationsPermissions(alertManagerSourceName);
|
||||
const canEditRoutes = contextSrv.hasPermission(permissions.update);
|
||||
const canDeleteRoutes = contextSrv.hasPermission(permissions.delete);
|
||||
const canReadProvisioning = contextSrv.hasAccess(permissions.provisioning.read, isOrgAdmin());
|
||||
const canReadProvisioning =
|
||||
contextSrv.hasAccess(permissions.provisioning.read, isOrgAdmin()) ||
|
||||
contextSrv.hasPermission(permissions.provisioning.readSecrets);
|
||||
|
||||
const contactPoint = currentRoute.receiver;
|
||||
const continueMatching = currentRoute.continue ?? false;
|
||||
|
@ -8,16 +8,23 @@ import {
|
||||
Receiver,
|
||||
} from 'app/plugins/datasource/alertmanager/types';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
import { ContactPointsState, NotifierDTO, NotifierType } from 'app/types';
|
||||
import { AccessControlAction, ContactPointsState, NotifierDTO, NotifierType } from 'app/types';
|
||||
|
||||
import * as onCallApi from '../../api/onCallApi';
|
||||
import * as receiversApi from '../../api/receiversApi';
|
||||
import { enableRBAC, grantUserPermissions } from '../../mocks';
|
||||
import { fetchGrafanaNotifiersAction } from '../../state/actions';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
||||
import { createUrl } from '../../utils/url';
|
||||
|
||||
import { ReceiversTable } from './ReceiversTable';
|
||||
import * as grafanaApp from './grafanaAppReceivers/grafanaApp';
|
||||
|
||||
const renderReceieversTable = async (receivers: Receiver[], notifiers: NotifierDTO[]) => {
|
||||
const renderReceieversTable = async (
|
||||
receivers: Receiver[],
|
||||
notifiers: NotifierDTO[],
|
||||
alertmanagerName = 'alertmanager-1'
|
||||
) => {
|
||||
const config: AlertManagerCortexConfig = {
|
||||
template_files: {},
|
||||
alertmanager_config: {
|
||||
@ -30,7 +37,7 @@ const renderReceieversTable = async (receivers: Receiver[], notifiers: NotifierD
|
||||
|
||||
return render(
|
||||
<TestProvider store={store}>
|
||||
<ReceiversTable config={config} alertManagerName="alertmanager-1" />
|
||||
<ReceiversTable config={config} alertManagerName={alertmanagerName} />
|
||||
</TestProvider>
|
||||
);
|
||||
};
|
||||
@ -127,4 +134,88 @@ describe('ReceiversTable', () => {
|
||||
expect(rows[1]).toHaveTextContent('without receivers');
|
||||
expect(rows[1].querySelector('[data-column="Type"]')).toHaveTextContent('');
|
||||
});
|
||||
|
||||
describe('RBAC Enabled', () => {
|
||||
describe('Export button', () => {
|
||||
const receivers: Receiver[] = [
|
||||
{
|
||||
name: 'with receivers',
|
||||
grafana_managed_receiver_configs: [mockGrafanaReceiver('googlechat'), mockGrafanaReceiver('sensugo')],
|
||||
},
|
||||
{
|
||||
name: 'no receivers',
|
||||
},
|
||||
];
|
||||
|
||||
const notifiers: NotifierDTO[] = [mockNotifier('googlechat', 'Google Chat'), mockNotifier('sensugo', 'Sensu Go')];
|
||||
|
||||
it('should be visible when user has permissions to read provisioning', async () => {
|
||||
enableRBAC();
|
||||
grantUserPermissions([AccessControlAction.AlertingProvisioningRead]);
|
||||
|
||||
await renderReceieversTable(receivers, notifiers, GRAFANA_RULES_SOURCE_NAME);
|
||||
|
||||
const buttons = within(screen.getByTestId('dynamic-table')).getAllByTestId('export');
|
||||
expect(buttons).toHaveLength(2);
|
||||
expect(buttons).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
href: createUrl(`http://localhost/api/v1/provisioning/contact-points/export/`, {
|
||||
download: 'true',
|
||||
format: 'yaml',
|
||||
decrypt: 'false',
|
||||
name: 'with receivers',
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
href: createUrl(`http://localhost/api/v1/provisioning/contact-points/export/`, {
|
||||
download: 'true',
|
||||
format: 'yaml',
|
||||
decrypt: 'false',
|
||||
name: 'no receivers',
|
||||
}),
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
it('should be visible when user has permissions to read provisioning with secrets', async () => {
|
||||
enableRBAC();
|
||||
grantUserPermissions([AccessControlAction.AlertingProvisioningReadSecrets]);
|
||||
|
||||
await renderReceieversTable(receivers, notifiers, GRAFANA_RULES_SOURCE_NAME);
|
||||
|
||||
const buttons = within(screen.getByTestId('dynamic-table')).getAllByTestId('export');
|
||||
expect(buttons).toHaveLength(2);
|
||||
expect(buttons).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
href: createUrl(`http://localhost/api/v1/provisioning/contact-points/export/`, {
|
||||
download: 'true',
|
||||
format: 'yaml',
|
||||
decrypt: 'true',
|
||||
name: 'with receivers',
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
href: createUrl(`http://localhost/api/v1/provisioning/contact-points/export/`, {
|
||||
download: 'true',
|
||||
format: 'yaml',
|
||||
decrypt: 'true',
|
||||
name: 'no receivers',
|
||||
}),
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
it('should not be visible when user has no provisioning permissions', async () => {
|
||||
enableRBAC();
|
||||
grantUserPermissions([AccessControlAction.AlertingNotificationsRead]);
|
||||
|
||||
await renderReceieversTable(receivers, [], GRAFANA_RULES_SOURCE_NAME);
|
||||
|
||||
const buttons = within(screen.getByTestId('dynamic-table')).queryAllByTestId('export');
|
||||
expect(buttons).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -69,6 +69,7 @@ interface ActionProps {
|
||||
delete: AccessControlAction;
|
||||
provisioning: {
|
||||
read: AccessControlAction;
|
||||
readSecrets: AccessControlAction;
|
||||
};
|
||||
};
|
||||
alertManagerName: string;
|
||||
@ -89,17 +90,20 @@ function ViewAction({ permissions, alertManagerName, receiverName }: ActionProps
|
||||
}
|
||||
|
||||
function ExportAction({ permissions, receiverName }: ActionProps) {
|
||||
const canReadSecrets = contextSrv.hasAccess(permissions.provisioning.readSecrets, isOrgAdmin());
|
||||
return (
|
||||
<Authorize actions={[permissions.provisioning.read]} fallback={isOrgAdmin()}>
|
||||
<Authorize actions={[permissions.provisioning.read, permissions.provisioning.readSecrets]}>
|
||||
<ActionIcon
|
||||
data-testid="export"
|
||||
to={createUrl(`/api/v1/provisioning/contact-points/export/`, {
|
||||
download: 'true',
|
||||
format: 'yaml',
|
||||
decrypt: isOrgAdmin().toString(),
|
||||
decrypt: canReadSecrets.toString(),
|
||||
name: receiverName,
|
||||
})}
|
||||
tooltip={isOrgAdmin() ? 'Export contact point' : 'Export redacted contact point'}
|
||||
tooltip={
|
||||
canReadSecrets ? 'Export contact point with decrypted secrets' : 'Export contact point with redacted secrets'
|
||||
}
|
||||
icon="download-alt"
|
||||
target="_blank"
|
||||
/>
|
||||
@ -301,7 +305,10 @@ export const ReceiversTable = ({ config, alertManagerName }: Props) => {
|
||||
const [showCannotDeleteReceiverModal, setShowCannotDeleteReceiverModal] = useState(false);
|
||||
|
||||
const isGrafanaAM = alertManagerName === GRAFANA_RULES_SOURCE_NAME;
|
||||
const showExport = isGrafanaAM && contextSrv.hasAccess(permissions.provisioning.read, isOrgAdmin());
|
||||
const showExport =
|
||||
isGrafanaAM &&
|
||||
(contextSrv.hasAccess(permissions.provisioning.read, isOrgAdmin()) ||
|
||||
contextSrv.hasAccess(permissions.provisioning.readSecrets, isOrgAdmin()));
|
||||
|
||||
const onClickDeleteReceiver = (receiverName: string): void => {
|
||||
if (isReceiverUsed(receiverName, config)) {
|
||||
@ -436,6 +443,7 @@ function useGetColumns(
|
||||
delete: AccessControlAction;
|
||||
provisioning: {
|
||||
read: AccessControlAction;
|
||||
readSecrets: AccessControlAction;
|
||||
};
|
||||
},
|
||||
isVanillaAM: boolean
|
||||
@ -498,7 +506,12 @@ function useGetColumns(
|
||||
label: 'Actions',
|
||||
renderCell: ({ data: { provisioned, name } }) => (
|
||||
<Authorize
|
||||
actions={[permissions.update, permissions.delete, permissions.provisioning.read]}
|
||||
actions={[
|
||||
permissions.update,
|
||||
permissions.delete,
|
||||
permissions.provisioning.read,
|
||||
permissions.provisioning.readSecrets,
|
||||
]}
|
||||
fallback={isOrgAdmin()}
|
||||
>
|
||||
<div className={tableStyles.actionsCell}>
|
||||
|
@ -50,6 +50,7 @@ export const notificationsPermissions = {
|
||||
|
||||
export const provisioningPermissions = {
|
||||
read: AccessControlAction.AlertingProvisioningRead,
|
||||
readSecrets: AccessControlAction.AlertingProvisioningReadSecrets,
|
||||
write: AccessControlAction.AlertingProvisioningWrite,
|
||||
};
|
||||
|
||||
@ -125,6 +126,8 @@ export function getRulesAccess() {
|
||||
rulesSourceName === GRAFANA_RULES_SOURCE_NAME ? contextSrv.hasEditPermissionInFolders : contextSrv.isEditor;
|
||||
return contextSrv.hasAccess(getRulesPermissions(rulesSourceName).update, permissionFallback);
|
||||
},
|
||||
canReadProvisioning: contextSrv.hasAccess(provisioningPermissions.read, isOrgAdmin()),
|
||||
canReadProvisioning:
|
||||
contextSrv.hasAccess(provisioningPermissions.read, isOrgAdmin()) ||
|
||||
contextSrv.hasAccess(provisioningPermissions.readSecrets, isOrgAdmin()),
|
||||
};
|
||||
}
|
||||
|
@ -118,6 +118,7 @@ export enum AccessControlAction {
|
||||
AlertingNotificationsExternalRead = 'alert.notifications.external:read',
|
||||
|
||||
// Alerting provisioning actions
|
||||
AlertingProvisioningReadSecrets = 'alert.provisioning.secrets:read',
|
||||
AlertingProvisioningRead = 'alert.provisioning:read',
|
||||
AlertingProvisioningWrite = 'alert.provisioning:write',
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user