Commit 79fea5df by Marcus Olsson Committed by GitHub

Docs: Update guide on authenticating data source plugins (#24884)

* Update guide on authenticating data source plugins

* Fix broken references
parent 9aef680e
...@@ -41,6 +41,7 @@ Ready to learn more? Check out our other tutorials: ...@@ -41,6 +41,7 @@ Ready to learn more? Check out our other tutorials:
Improve an existing plugin with one of our guides: Improve an existing plugin with one of our guides:
- [Add authentication for data source plugins]({{< relref "add-authentication-for-data-source-plugins" >}})
- [Add support for annotations]({{< relref "add-support-for-annotations.md" >}}) - [Add support for annotations]({{< relref "add-support-for-annotations.md" >}})
- [Add support for Explore queries]({{< relref "add-support-for-explore-queries.md" >}}) - [Add support for Explore queries]({{< relref "add-support-for-explore-queries.md" >}})
- [Add support for variables]({{< relref "add-support-for-variables.md" >}}) - [Add support for variables]({{< relref "add-support-for-variables.md" >}})
...@@ -52,7 +53,6 @@ Improve an existing plugin with one of our guides: ...@@ -52,7 +53,6 @@ Improve an existing plugin with one of our guides:
Deepen your knowledge through a series of high-level overviews of plugin concepts: Deepen your knowledge through a series of high-level overviews of plugin concepts:
- [Data frames]({{< relref "data-frames.md" >}}) - [Data frames]({{< relref "data-frames.md" >}})
- [Authentication for data source plugins]({{< relref "authentication.md" >}})
### UI library ### UI library
......
+++
title = "Add authentication for data source plugins"
type = "docs"
aliases = ["/docs/grafana/latest/plugins/developing/auth-for-datasources/", "/docs/grafana/latest/developers/plugins/authentication/"]
+++
# Add authentication for data source plugins
This page explains how to use the Grafana data source proxy to authenticate against an third-party API from a data source plugin.
When a user saves a password or any other sensitive data as a data source option, Grafana encrypts the data and stores it in the Grafana database. Any encrypted data source options can only be decrypted on the Grafana server. This means that any data source that makes authenticated queries needs to request the decrypted data to be sent to the browser.
To minimize the amount of sensitive information sent to and from the browser, data source plugins can use the Grafana _data source proxy_. When using the data source proxy, any requests containing sensitive information go through the Grafana server. No sensitive data is sent to the browser after the data is saved.
Some data sources, like [Prometheus](https://grafana.com/docs/grafana/latest/features/datasources/prometheus/) and [InfluxDB](https://grafana.com/docs/grafana/latest/features/datasources/influxdb/), allow users to configure whether to use the data source proxy, through a setting called _access modes_.
## Add a proxy route to your plugin
To forward requests through the Grafana proxy, you need to configure one or more _routes_. A route specifies how the proxy transforms outgoing requests. All routes for a given plugin are defined in the [plugin.json]({{< relref "metadata.md" >}}) file.
Let's add a route to proxy requests to `https://api.example.com/foo/bar`.
1. Add the route to `plugin.json`. Note that you need to reload the Grafana server every time you make a change to your `plugin.json` file.
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com"
}
]
```
1. In the `DataSource`, extract the proxy URL from `instanceSettings` to a class property called `url`.
```ts
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
url?: string;
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
super(instanceSettings);
this.url = instanceSettings.url;
}
// ...
}
```
1. In the `query` method, make a request using [BackendSrv]({{< relref "../../packages_api/runtime/backendsrv.md" >}}).
```ts
const routePath = '/example';
getBackendSrv()
.datasourceRequest({
url: this.url + routePath + '/foo/bar',
method: 'GET',
});
```
## Add a dynamic proxy route to your plugin
In the example above, the URL stays the same for everyone using the plugin. Let's look at how you can create dynamic routes based on data source options that are provided by the user.
Many of the properties in the `route` object can use templates in the form of `{{ .JsonData.YOUR_OPTION_NAME }}`, where `YOUR_OPTION_NAME` is the name of a property in the `jsonData` object.
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com/projects/{{ .JsonData.projectId }}"
}
]
```
You can also access sensitive data in your route configuration by changing `.JsonData` into `.SecureJsonData`.
```json
"routes": [
{
"path": "example",
"url": "https://{{ .JsonData.username }}:{{ .SecureJsonData.password }}@api.example.com"
}
]
```
Now you know how to define routes for your data source plugin. Next, let's look at how to authenticate requests for your routes.
## Configure the authentication method for a route
The Grafana proxy supports a number of different authentication methods. For more information on how to configure each authentication method, refer to [plugin.json]({{< relref "metadata.md" >}}).
For any sensitive data, make sure that you encrypt data source options, and that you use `{{ .SecureJsonData.YOUR_OPTION_NAME }}` when using sensitive data source options in your routes.
### Add HTTP header
To add HTTP headers to proxied requests, use the `headers` property.
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com",
"headers": [
{
"name": "Authorization",
"content": "Bearer {{ .SecureJsonData.apiToken }}"
}
]
}
]
```
### Add URL parameters
To add URL parameters to proxied requests, use the `urlParams` property.
```json
"routes": [
{
"path": "example",
"url": "http://api.example.com",
"urlParams": [
{
"name": "apiKey",
"content": "{{ .SecureJsonData.apiKey }}"
}
]
}
]
```
### Enable token authentication
To enable token-based authentication for proxied requests, use the `tokenAuth` property.
Grafana automatically renews the token when it expires.
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com",
"tokenAuth": {
"url": "https://login.example.com/oauth2/token",
"params": {
"grant_type": "client_credentials",
"client_id": "{{ .JsonData.clientId }}",
"client_secret": "{{ .SecureJsonData.clientSecret }}"
}
}
}
]
```
+++
title = "Authentication for data source plugins"
type = "docs"
aliases = ["/docs/grafana/latest/plugins/developing/auth-for-datasources/"]
+++
# Authentication for data source plugins
Grafana has a proxy feature that proxies all data requests through the Grafana backend. The main benefit of using the proxy is secure handling of credentials when authenticating against an external/third-party API. The Grafana proxy also adds [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) headers to the proxied requests.
The proxy supports:
- [authentication with HTTP Headers]({{< relref "#api-key-http-header-authentication" >}}).
- [token authentication]({{< relref "#how-token-authentication-works" >}}) and can automatically renew a token for the user when the token expires.
## How the proxy works
The user saves the API key/password on the plugin config page and it is encrypted (using the `secureJsonData` feature) and saved in the Grafana database. When a request from the data source is made, the Grafana proxy will:
1. Intercept the original request sent from the data source plugin.
1. Load the `secureJsonData` data from the database and decrypt the API key or password on the Grafana backend.
1. If using token authentication, carry out authentication and generate an OAuth token that will be added as an `Authorization` HTTP header to the requests (or alternatively it will add a HTTP header with the API key).
1. Renew the token if it has expired.
1. After adding CORS headers and authorization headers, forward the request to the external API.
This means that users that access the data source config page cannot access the API key or password after they have saved it the first time and that no secret keys are sent in plain text through the browser where they can be spied on.
For backend authentication to work, the external/third-party API must either have an OAuth endpoint or that the API accepts an API key as a HTTP header for authentication.
## Encrypting sensitive data
When a user saves a password or secret with your data source plugin's Config page, then you can save data in an encrypted blob in the Grafana database called `secureJsonData`. Any data saved in the blob is encrypted by Grafana and can only be decrypted by the Grafana server on the backend. This means once a password is saved, no sensitive data is sent to the browser. If the password is saved in the `jsonData` blob or the `password` field then it is unencrypted and anyone with Admin access (with the help of Chrome Developer Tools) can read it.
This is an example of using the `secureJsonData` blob to save a property called `password` in a html input:
```html
<input type="password" class="gf-form-input" ng-model="ctrl.current.secureJsonData.password" placeholder="password" />
```
## Plugin routes
A plugin route describes where the intercepted request should be forwarded to and how to authenticate for the external API. You can define multiple routes that can match multiple external API endpoints.
You specify routes in the `plugin.json` file for your data source plugin. [Here is an example](https://github.com/grafana/azure-monitor-datasource/blob/d74c82145c0a4af07a7e96cc8dde231bfd449bd9/src/plugin.json#L30-L95) with lots of routes (though most plugins will just have one route).
When you build your URL to the third-party API in your data source class, the URL should start with the text specified in the path field for a route. The proxy will strip out the path text and replace it with the value in the URL field.
### Simple plugin route example
- If my code makes a call to URL `azuremonitor/foo/bar` with this code:
```js
this.backendSrv.datasourceRequest({
url: url,
method: "GET",
});
```
- and the plugin has this route:
```json
"routes": [{
"path": "azuremonitor",
"method": "GET",
"url": "https://management.azure.com"
}]
```
- then the Grafana proxy will transform the URL from the original request into `https://management.azure.com/foo/bar`
- finally, it will add CORS headers and forward the request to the new URL. This example does not do any authentication.
The `method` parameter is optional. It can be set to a specific HTTP verb to provide more fine-grained control. For example you might have two plugin routes, one for GET requests and one for POST requests.
### Dynamic routes
When using routes, you can also reference a variable stored in JsonData or SecureJsonData which is interpolated (replacing the variable text with a value) when the data source makes a request to the proxy. These are variables that were entered by the user on the data source configuration page and saved in the Grafana database.
In this example, the value for `dynamicUrl` comes from the JsonData blob and the api key's value is set from the SecureJsonData blob. The `urlParams` field is for query string parameters for HTTP GET requests.
```json
"routes": [
{
"path": "custom/api/v5/*",
"method": "GET",
"url": "{{.JsonData.dynamicUrl}}",
"urlParams": [
{"name": "apiKey", "content": "{{.SecureJsonData.apiKey}}"}
]
}
]
```
Given that:
- `JsonData.dynamicUrl` has the value `http://example.com/api`
- `SecureJsonData.apiKey` has the value `secretKey`
a call to the URL: `custom/api/v5/some/path`
will be proxied to the following URL: `http://example.com/api/some/path?apiKey=secretKey`
An app using this feature can be found [here](https://github.com/grafana/kentik-app).
## API key/HTTP header authentication
Some third-party API's accept a HTTP Header for authentication. The [example](https://github.com/grafana/azure-monitor-datasource/blob/d74c82145c0a4af07a7e96cc8dde231bfd449bd9/src/plugin.json#L91-L93) below has a `headers` section that defines the name of the HTTP Header that the API expects and it uses the `SecureJSONData` blob to fetch an encrypted API key. The Grafana server proxy will decrypt the key, add the `X-API-Key` header to the request and forward it to the third-party API.
```json
{
"path": "appinsights",
"method": "GET",
"url": "https://api.applicationinsights.io",
"headers": [{ "name": "X-API-Key", "content": "{{.SecureJsonData.appInsightsApiKey}}" }]
}
```
## How token authentication works
The token auth section in the `plugin.json` file looks like this:
```json
"tokenAuth": {
"url": "https://login.microsoftonline.com/{{.JsonData.tenantId}}/oauth2/token",
"params": {
"grant_type": "client_credentials",
"client_id": "{{.JsonData.clientId}}",
"client_secret": "{{.SecureJsonData.clientSecret}}",
"resource": "https://management.azure.com/"
}
}
```
This interpolates in data from both `jsonData` and `secureJsonData` to generate the token request to the third-party API. It is common for tokens to have a short expiry period (30 minutes). The Grafana proxy automatically renews the token if it has expired.
## Always restart the Grafana server after route changes
The plugin.json files are only loaded when the Grafana server starts so when a route is added or changed then the Grafana server has to be restarted for the changes to take effect.
...@@ -154,7 +154,7 @@ A basic guide for data sources can be found [here](http://docs.grafana.org/plugi ...@@ -154,7 +154,7 @@ A basic guide for data sources can be found [here](http://docs.grafana.org/plugi
If possible, any passwords or secrets should be be saved in the `secureJsonData` blob. To encrypt sensitive data, the Grafana server's proxy feature must be used. The Grafana server has support for token authentication (OAuth) and HTTP Header authentication. If the calls have to be sent directly from the browser to a third-party API, this will not be possible and sensitive data will not be encrypted. If possible, any passwords or secrets should be be saved in the `secureJsonData` blob. To encrypt sensitive data, the Grafana server's proxy feature must be used. The Grafana server has support for token authentication (OAuth) and HTTP Header authentication. If the calls have to be sent directly from the browser to a third-party API, this will not be possible and sensitive data will not be encrypted.
Read more here about how [authentication for data sources]({{< relref "../authentication.md" >}}) works. Read more here about how [authentication for data sources]({{< relref "../add-authentication-for-data-source-plugins.md" >}}) works.
If using the proxy feature, the Config page should use the `secureJsonData` blob like this: If using the proxy feature, the Config page should use the `secureJsonData` blob like this:
......
...@@ -226,7 +226,7 @@ Content-Type: application/json ...@@ -226,7 +226,7 @@ Content-Type: application/json
} }
``` ```
> NOTE: `password` and `basicAuthPassword` should be defined under `secureJsonData` in order to be stored securely as an encrypted blob in the database. Then, the encrypted fields are listed under `secureJsonFields` section in the response. See also the [Encrypting Sensitive Data]({{< relref "../developers/plugins/authentication.md/#encrypting-sensitive-data">}}) documentation for more details. > **Note:** By defining `password` and `basicAuthPassword` under `secureJsonData` Grafana encrypts them securely as an encrypted blob in the database. The response then lists the encrypted fields under `secureJsonFields`.
**Example Graphite Request with basic auth enabled**: **Example Graphite Request with basic auth enabled**:
...@@ -376,7 +376,7 @@ Content-Type: application/json ...@@ -376,7 +376,7 @@ Content-Type: application/json
} }
``` ```
> NOTE: Similar to [creating a data source](#create-a-data-source), `password` and `basicAuthPassword` should be defined under `secureJsonData` in order to be stored securely as an encrypted blob in the database. Then, the encrypted fields are listed under `secureJsonFields` section in the response. See also the [Encrypting Sensitive Data]({{< relref "../developers/plugins/authentication.md/#encrypting-sensitive-data">}}) documentation for more details. > **Note:** Similar to [creating a data source](#create-a-data-source), `password` and `basicAuthPassword` should be defined under `secureJsonData` in order to be stored securely as an encrypted blob in the database. Then, the encrypted fields are listed under `secureJsonFields` section in the response.
## Delete an existing data source by id ## Delete an existing data source by id
......
...@@ -31,154 +31,154 @@ ...@@ -31,154 +31,154 @@
- name: Administration - name: Administration
link: /administration/ link: /administration/
children: children:
- name: Configuration - name: Configuration
link: /installation/configuration/ link: /installation/configuration/
- name: Configure Docker image - name: Configure Docker image
link: /installation/configure-docker/ link: /installation/configure-docker/
- name: Security - name: Security
link: /installation/security/ link: /installation/security/
- name: Authentication - name: Authentication
link: /auth/ link: /auth/
children: children:
- link: /auth/overview/ - link: /auth/overview/
name: Overview name: Overview
- link: /auth/auth-proxy/ - link: /auth/auth-proxy/
name: Auth Proxy name: Auth Proxy
- link: /auth/ldap/ - link: /auth/ldap/
name: LDAP name: LDAP
- link: /auth/enhanced_ldap/ - link: /auth/enhanced_ldap/
name: Enhanced LDAP name: Enhanced LDAP
- link: /auth/generic-oauth/ - link: /auth/generic-oauth/
name: Generic OAuth name: Generic OAuth
- link: /auth/google/ - link: /auth/google/
name: Google name: Google
- link: /auth/azuread/ - link: /auth/azuread/
name: Azure AD name: Azure AD
- link: /auth/github/ - link: /auth/github/
name: GitHub name: GitHub
- link: /auth/gitlab/ - link: /auth/gitlab/
name: GitLab name: GitLab
- link: /auth/okta/ - link: /auth/okta/
name: Okta name: Okta
- link: /auth/saml/ - link: /auth/saml/
name: SAML name: SAML
- link: /auth/team-sync/ - link: /auth/team-sync/
name: Team Sync name: Team Sync
- name: Permissions - name: Permissions
link: /permissions/ link: /permissions/
children: children:
- link: /permissions/overview/ - link: /permissions/overview/
name: Overview name: Overview
- link: /permissions/organization_roles/ - link: /permissions/organization_roles/
name: Organization Roles name: Organization Roles
- link: /permissions/dashboard_folder_permissions/ - link: /permissions/dashboard_folder_permissions/
name: Dashboard and Folder name: Dashboard and Folder
- link: /permissions/datasource_permissions/ - link: /permissions/datasource_permissions/
name: Data source name: Data source
- name: Grafana CLI - name: Grafana CLI
link: /administration/cli/ link: /administration/cli/
- name: Internal metrics - name: Internal metrics
link: /administration/metrics/ link: /administration/metrics/
- name: Provisioning - name: Provisioning
link: /administration/provisioning/ link: /administration/provisioning/
- name: Set up Grafana for high availability - name: Set up Grafana for high availability
link: /tutorials/ha_setup/ link: /tutorials/ha_setup/
- name: Troubleshooting - name: Troubleshooting
link: /installation/troubleshooting/ link: /installation/troubleshooting/
- name: Manage users - name: Manage users
link: /manage-users link: /manage-users
children: children:
- link: /manage-users/ - link: /manage-users/
name: Overview name: Overview
- link: /manage-users/add-or-remove-user/ - link: /manage-users/add-or-remove-user/
name: Add or remove a user name: Add or remove a user
- link: /manage-users/enable-or-disable-user/ - link: /manage-users/enable-or-disable-user/
name: Enable or disable a user name: Enable or disable a user
- link: /manage-users/create-or-remove-team/ - link: /manage-users/create-or-remove-team/
name: Create or remove a team name: Create or remove a team
- link: /manage-users/add-or-remove-user-from-team/ - link: /manage-users/add-or-remove-user-from-team/
name: Add or remove user from team name: Add or remove user from team
- name: Data sources - name: Data sources
link: /features/datasources/ link: /features/datasources/
children: children:
- link: /features/datasources/add-a-data-source/ - link: /features/datasources/add-a-data-source/
name: Add data source name: Add data source
- link: /features/datasources/cloudwatch/ - link: /features/datasources/cloudwatch/
name: AWS Cloudwatch name: AWS Cloudwatch
- link: /features/datasources/azuremonitor/ - link: /features/datasources/azuremonitor/
name: Azure Monitor name: Azure Monitor
- link: /features/datasources/elasticsearch/ - link: /features/datasources/elasticsearch/
name: Elasticsearch name: Elasticsearch
- link: /features/datasources/stackdriver/ - link: /features/datasources/stackdriver/
name: Google Stackdriver name: Google Stackdriver
- link: /features/datasources/graphite/ - link: /features/datasources/graphite/
name: Graphite name: Graphite
- link: /features/datasources/influxdb/ - link: /features/datasources/influxdb/
name: InfluxDB name: InfluxDB
- link: /features/datasources/jaeger/ - link: /features/datasources/jaeger/
name: Jaeger name: Jaeger
- link: /features/datasources/loki/ - link: /features/datasources/loki/
name: Loki name: Loki
- link: /features/datasources/mssql/ - link: /features/datasources/mssql/
name: Microsoft SQL Server name: Microsoft SQL Server
- link: /features/datasources/mysql/ - link: /features/datasources/mysql/
name: MySQL name: MySQL
- link: /features/datasources/opentsdb/ - link: /features/datasources/opentsdb/
name: OpenTSDB name: OpenTSDB
- link: /features/datasources/postgres/ - link: /features/datasources/postgres/
name: PostgreSQL name: PostgreSQL
- link: /features/datasources/prometheus/ - link: /features/datasources/prometheus/
name: Prometheus name: Prometheus
- link: /features/datasources/testdata/ - link: /features/datasources/testdata/
name: TestData DB name: TestData DB
- link: /features/datasources/zipkin/ - link: /features/datasources/zipkin/
name: Zipkin name: Zipkin
- name: Panels - name: Panels
link: /panels/ link: /panels/
children: children:
- link: /panels/panels-overview/ - link: /panels/panels-overview/
name: Overview name: Overview
- link: /panels/add-a-panel/ - link: /panels/add-a-panel/
name: Add panel name: Add panel
- link: /panels/queries/ - link: /panels/queries/
name: Queries name: Queries
- link: /panels/transformations/ - link: /panels/transformations/
name: Transformations name: Transformations
- link: /panels/field-configuration-options/ - link: /panels/field-configuration-options/
name: Field config options name: Field config options
- link: /panels/panel-editor/ - link: /panels/panel-editor/
name: Panel editor name: Panel editor
- name: Visualizations - name: Visualizations
link: /panels/visualizations/ link: /panels/visualizations/
children: children:
- link: /panels/visualizations/alert-list-panel/ - link: /panels/visualizations/alert-list-panel/
name: Alert list name: Alert list
- link: /panels/visualizations/bar-gauge-panel/ - link: /panels/visualizations/bar-gauge-panel/
name: Bar gauge name: Bar gauge
- link: /panels/visualizations/dashboard-list-panel/ - link: /panels/visualizations/dashboard-list-panel/
name: Dashboard list name: Dashboard list
- link: /panels/visualizations/gauge-panel/ - link: /panels/visualizations/gauge-panel/
name: Gauge name: Gauge
- link: /panels/visualizations/graph-panel/ - link: /panels/visualizations/graph-panel/
name: Graph name: Graph
- link: /panels/visualizations/heatmap/ - link: /panels/visualizations/heatmap/
name: Heatmap name: Heatmap
- link: /panels/visualizations/logs-panel/ - link: /panels/visualizations/logs-panel/
name: Logs name: Logs
- link: /panels/visualizations/news-panel/ - link: /panels/visualizations/news-panel/
name: News name: News
- link: /panels/visualizations/stat-panel/ - link: /panels/visualizations/stat-panel/
name: Stat name: Stat
- link: /panels/visualizations/table-panel/ - link: /panels/visualizations/table-panel/
name: Table name: Table
- link: /panels/visualizations/text-panel/ - link: /panels/visualizations/text-panel/
name: Text name: Text
- link: /panels/thresholds/ - link: /panels/thresholds/
name: Thresholds name: Thresholds
- link: /panels/inspect-panel/ - link: /panels/inspect-panel/
name: Inspect panel name: Inspect panel
- link: /panels/calculations-list/ - link: /panels/calculations-list/
name: Calculations list name: Calculations list
- name: Dashboards - name: Dashboards
link: /features/dashboard/ link: /features/dashboard/
children: children:
...@@ -412,6 +412,8 @@ ...@@ -412,6 +412,8 @@
link: /developers/plugins/authentication/ link: /developers/plugins/authentication/
- name: Sign a plugin - name: Sign a plugin
link: /developers/plugins/sign-a-plugin/ link: /developers/plugins/sign-a-plugin/
- name: Add authentication for data source plugins
link: /developers/plugins/add-authentication-for-data-source-plugins/
- name: Backend plugins - name: Backend plugins
children: children:
- link: /developers/plugins/backend/ - link: /developers/plugins/backend/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment