Plugins doc review chunk 2 (#67691)

* Doc style edit for 7 topics

Signed-off-by: Joe Perez <joseph.perez@grafana.com>

* Proofread topics

Signed-off-by: Joe Perez <joseph.perez@grafana.com>

* Prettier

Signed-off-by: Joe Perez <joseph.perez@grafana.com>

* Doc fix

Signed-off-by: Joe Perez <joseph.perez@grafana.com>

* Update docs/sources/developers/plugins/add-query-editor-help.md

Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>

* Doc fixes

Signed-off-by: Joe Perez <joseph.perez@grafana.com>

* Changes from doc review

Signed-off-by: Joe Perez <joseph.perez@grafana.com>

* Incorporate review feedback

Signed-off-by: Joe Perez <joseph.perez@grafana.com>

* More fixes

Signed-off-by: Joe Perez <joseph.perez@grafana.com>

* More doc fixes

Signed-off-by: Joe Perez <joseph.perez@grafana.com>

---------

Signed-off-by: Joe Perez <joseph.perez@grafana.com>
Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com>
This commit is contained in:
Joseph Perez
2023-05-12 11:33:49 -07:00
committed by GitHub
parent 8dee5d3364
commit 49a18bc8e2
7 changed files with 248 additions and 212 deletions

View File

@ -7,112 +7,121 @@ title: Add authentication for data source plugins
# Add authentication for data source plugins
This page explains how to configure your data source plugin to authenticate against a third-party API.
Grafana plugins can perform authenticated requests against a third-party API by using the _data source proxy_ or through a custom a _backend plugin_.
There are two ways you can perform authenticated requests from your plugin—using the [_data source proxy_](#authenticate-using-the-data-source-proxy), or by building a [_backend plugin_](#authenticate-using-a-backend-plugin). The one you choose depends on how your plugin authenticates against the third-party API.
## Choose an authentication method
- Use the data source proxy if you need to authenticate using Basic Auth or API keys
- Use the data source proxy if the API supports OAuth 2.0 using client credentials
- Use a backend plugin if the API uses a custom authentication method that isn't supported by the data source proxy, or if your API communicates over a different protocol than HTTP
Configure your data source plugin to authenticate against a third-party API in one of either of two ways:
Regardless of which approach you use, you first need to encrypt any sensitive information that the plugin needs to store.
- Use the [_data source proxy_](#authenticate-using-the-data-source-proxy) method, or
- Build a [_backend plugin_](#authenticate-using-a-backend-plugin).
| Case | Use |
| ----------------------------------------------------------------------------------------------- | ------------------------------- |
| Do you need to authenticate your plugin using Basic Auth or API keys? | Use the data source proxy. |
| Does your API support OAuth 2.0 using client credentials? | Use the data source proxy. |
| Does your API use a custom authentication method that isn't supported by the data source proxy? | Use a backend plugin. |
| Does your API communicate over a protocol other than HTTP? | Build and use a backend plugin. |
| Does your plugin require alerting support? | Build and use a backend plugin. |
## Encrypt data source configuration
Data source plugins have two ways of storing custom configuration: `jsonData` and `secureJsonData`.
Users with the _Viewer_ role can access data source configurationsuch as the contents of `jsonData`in cleartext. If you've enabled anonymous access, anyone that can access Grafana in their browser can see the contents of `jsonData`. **Only use `jsonData` to store non-sensitive configuration.**
Users with the Viewer role can access data source configuration such as the contents of `jsonData` in cleartext. If you've enabled anonymous access, anyone who can access Grafana in their browser can see the contents of `jsonData`.
Users of [Grafana Enterprise](https://grafana.com/products/enterprise/grafana/) can restrict access to data sources to specific users and teams. For more information, refer to [Data source permissions](https://grafana.com/docs/grafana/latest/enterprise/datasource_permissions).
> **Important:** Do not use `jsonData` with sensitive data such as password, tokens, and API keys. If you need to store sensitive information, use `secureJsonData` instead.
> **Note:** You can see the settings that the current user has access to by entering `window.grafanaBootData` in the developer console of your browser.
> **Note:** Users of [Grafana Enterprise](https://grafana.com/products/enterprise/grafana/) can restrict access to data sources to specific users and teams. For more information, refer to [Data source permissions](https://grafana.com/docs/grafana/latest/enterprise/datasource_permissions).
### Store configuration in `secureJsonData`
If you need to store sensitive information, such as passwords, tokens and API keys, use `secureJsonData` instead. Whenever the user saves the data source configuration, the secrets in `secureJsonData` are sent to the Grafana server and encrypted before they're stored.
If you need to store sensitive information, use `secureJsonData` instead of `jsonData`. Whenever the user saves the data source configuration, the secrets in `secureJsonData` are sent to the Grafana server and encrypted before they're stored.
Once the secure configuration has been encrypted, it can no longer be accessed from the browser. The only way to access secrets after they've been saved is by using the [_data source proxy_](#authenticate-using-the-data-source-proxy).
Once you have encrypted the secure configuration, it can no longer be accessed from the browser. The only way to access secrets after they've been saved is by using the [_data source proxy_](#authenticate-using-the-data-source-proxy).
#### Add secret configuration to your data source plugin
### Add secret configuration to your data source plugin
To demonstrate how you can add secrets to a data source plugin, let's add support for configuring an API key.
Create a new interface in `types.ts` to hold the API key.
1. Create a new interface in `types.ts` to hold the API key:
```ts
export interface MySecureJsonData {
apiKey?: string;
}
```
1. Add type information to your `secureJsonData` object by updating the props for your `ConfigEditor` to accept the interface as a second type parameter. Access the value of the secret from the `options` prop inside your `ConfigEditor`:
```ts
export interface MySecureJsonData {
apiKey?: string;
}
```
```ts
interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions, MySecureJsonData> {}
```
Add type information to your `secureJsonData` object by updating the props for your `ConfigEditor` to accept the interface as a second type parameter.
```ts
const { secureJsonData, secureJsonFields } = options;
const { apiKey } = secureJsonData;
```
```ts
interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions, MySecureJsonData> {}
```
> **Note:** You can do this until the user saves the configuration; when the user saves the configuration, Grafana clears the value. After that, you can use `secureJsonFields` to determine whether the property has been configured.
You can access the value of the secret from the `options` prop inside your `ConfigEditor` until the user saves the configuration. When the user saves the configuration, Grafana clears the value. After that, you can use the `secureJsonFields` to determine whether the property has been configured.
1. To securely update the secret in your plugin's configuration editor, update the `secureJsonData` object using the `onOptionsChange` prop:
```ts
const { secureJsonData, secureJsonFields } = options;
const { apiKey } = secureJsonData;
```
```ts
const onAPIKeyChange = (event: ChangeEvent<HTMLInputElement>) => {
onOptionsChange({
...options,
secureJsonData: {
apiKey: event.target.value,
},
});
};
```
To securely update the secret in your plugin's configuration editor, update the `secureJsonData` object using the `onOptionsChange` prop.
1. Define a component that can accept user input:
```ts
const onAPIKeyChange = (event: ChangeEvent<HTMLInputElement>) => {
onOptionsChange({
...options,
secureJsonData: {
apiKey: event.target.value,
},
});
};
```
```ts
<Input
type="password"
placeholder={secureJsonFields?.apiKey ? 'configured' : ''}
value={secureJsonData.apiKey ?? ''}
onChange={onAPIKeyChange}
/>
```
Next, define a component that can accept user input.
1. Optional: If you want the user to be able to reset the API key, then you need to set the property to `false` in the `secureJsonFields` object:
```ts
<Input
type="password"
placeholder={secureJsonFields?.apiKey ? 'configured' : ''}
value={secureJsonData.apiKey ?? ''}
onChange={onAPIKeyChange}
/>
```
Finally, if you want the user to be able to reset the API key, then you need to set the property to `false` in the `secureJsonFields` object.
```ts
const onResetAPIKey = () => {
onOptionsChange({
...options,
secureJsonFields: {
...options.secureJsonFields,
apiKey: false,
},
secureJsonData: {
...options.secureJsonData,
apiKey: '',
},
});
};
```
```ts
const onResetAPIKey = () => {
onOptionsChange({
...options,
secureJsonFields: {
...options.secureJsonFields,
apiKey: false,
},
secureJsonData: {
...options.secureJsonData,
apiKey: '',
},
});
};
```
Now that users can configure secrets, the next step is to see how we can add them to our requests.
## Authenticate using the data source proxy
Once the user has saved the configuration for a data source, any secret data source configuration will no longer be available in the browser. Encrypted secrets can only be accessed on the server. So how do you add them to you request?
Once the user has saved the configuration for a data source, the secret data source configuration will no longer be available in the browser. Encrypted secrets can only be accessed on the server. So how do you add them to your request?
The Grafana server comes with a proxy that lets you define templates for your requests. We call them _proxy routes_. Grafana sends the proxy route to the server, decrypts the secrets along with other configuration, and adds them to the request before sending it off.
The Grafana server comes with a proxy that lets you define templates for your requests: _proxy routes_. Grafana sends the proxy route to the server, decrypts the secrets along with other configuration, and adds them to the request before sending it.
> **Note:** Be sure not to confuse the data source proxy with the [auth proxy]({{< relref "../../setup-grafana/configure-security/configure-authentication/auth-proxy/" >}}). The data source proxy is used to authenticate a data source, while the auth proxy is used to log into Grafana itself.
### Add a proxy route to your plugin
To forward requests through the Grafana proxy, you need to configure one or more proxy routes. A proxy route is a template for any outgoing request that is handled by the proxy. You can configure proxy routes in the [plugin.json](https://grafana.com/docs/grafana/latest/developers/plugins/metadata/) file.
To forward requests through the Grafana proxy, you need to configure one or more _proxy routes_. A proxy route is a template for any outgoing request that is handled by the proxy. You can configure proxy routes in the [plugin.json](https://grafana.com/docs/grafana/latest/developers/plugins/metadata/) file.
1. Add the route to plugin.json. Note that you need to restart the Grafana server every time you make a change to your plugin.json file.
1. Add the route to `plugin.json`:
```json
"routes": [
@ -123,7 +132,9 @@ To forward requests through the Grafana proxy, you need to configure one or more
]
```
1. In the `DataSource`, extract the proxy URL from `instanceSettings` to a class property called `url`.
> **Note:** You need to restart the Grafana server every time you make a change to your `plugin.json` file.
1. In the `DataSource`, extract the proxy URL from `instanceSettings` to a class property called `url`:
```ts
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
@ -139,7 +150,7 @@ To forward requests through the Grafana proxy, you need to configure one or more
}
```
1. In the `query` method, make a request using `BackendSrv`. The first section of the URL path needs to match the `path` of your proxy route. The data source proxy replaces `this.url + routePath` with the `url` of the route. The following request will be made to `https://api.example.com/v1/users`.
1. In the `query` method, make a request using `BackendSrv`. The first section of the URL path needs to match the `path` of your proxy route. The data source proxy replaces `this.url + routePath` with the `url` of the route. Based on our example, the URL for the request would be `https://api.example.com/v1/users`:
```ts
import { getBackendSrv } from '@grafana/runtime';
@ -158,32 +169,36 @@ To forward requests through the Grafana proxy, you need to configure one or more
Grafana sends the proxy route to the server, where the data source proxy decrypts any sensitive data and interpolates the template variables with the decrypted data before making the request.
To add user-defined configuration to your routes, for example, add `{{ .JsonData.projectId }}` to the route, where `projectId` is the name of a property in the `jsonData` object.
To add user-defined configuration to your routes:
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com/projects/{{ .JsonData.projectId }}"
}
]
```
- Use `.JsonData` for configuration stored in `jsonData`. For example, where `projectId` is the name of a property in the `jsonData` object:
You can also configure your route to use sensitive data by using `.SecureJsonData`.
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com/projects/{{ .JsonData.projectId }}"
}
]
```
```json
"routes": [
{
"path": "example",
"url": "https://{{ .JsonData.username }}:{{ .SecureJsonData.password }}@api.example.com"
}
]
```
- Use `.SecureJsonData` for sensitive data stored in `secureJsonData`. For example, where `password` is the name of a property in the `secureJsonData` object:
In addition to the URL, you can also add headers, URL parameters, and a request body, to a proxy route.
```json
"routes": [
{
"path": "example",
"url": "https://{{ .JsonData.username }}:{{ .SecureJsonData.password }}@api.example.com"
}
]
```
In addition to adding the URL to the proxy route, you can also add headers, URL parameters, and a request body.
#### Add HTTP headers to a proxy route
Here's an example of adding `name` and `content` as HTTP headers:
```json
"routes": [
{
@ -201,6 +216,8 @@ In addition to the URL, you can also add headers, URL parameters, and a request
#### Add URL parameters to a proxy route
Here's an example of adding `name` and `content` as URL parameters:
```json
"routes": [
{
@ -218,6 +235,8 @@ In addition to the URL, you can also add headers, URL parameters, and a request
#### Add a request body to a proxy route
Here's an example of adding `username` and `password` to the request body:
```json
"routes": [
{
@ -231,11 +250,9 @@ In addition to the URL, you can also add headers, URL parameters, and a request
]
```
### Add a OAuth 2.0 proxy route to your plugin
### Add an OAuth 2.0 proxy route to your plugin
The data source proxy supports OAuth 2.0 authentication.
Since the request to each route is made server-side, only machine-to-machine authentication is supported. In order words, if you need to use a different grant than client credentials, you need to implement it yourself.
Since your request to each route is made server-side with OAuth 2.0 authentication, only machine-to-machine requests are supported. In order words, if you need to use a different grant than client credentials, you need to implement it yourself.
To authenticate using OAuth 2.0, add a `tokenAuth` object to the proxy route definition. If necessary, Grafana performs a request to the URL defined in `tokenAuth` to retrieve a token before making the request to the URL in your proxy route. Grafana automatically renews the token when it expires.
@ -264,10 +281,10 @@ Any parameters defined in `tokenAuth.params` are encoded as `application/x-www-f
While the data source proxy supports the most common authentication methods for HTTP APIs, using proxy routes has a few limitations:
- Proxy routes only support HTTP or HTTPS
- Proxy routes don't support custom token authentication
- Proxy routes only support HTTP or HTTPS.
- Proxy routes don't support custom token authentication.
If any of these limitations apply to your plugin, you need to add a [backend plugin]({{< relref "backend/" >}}). Since backend plugins run on the server they can access decrypted secrets, which makes it easier to implement custom authentication methods.
If any of these limitations apply to your plugin, you need to add a [backend plugin]({{< relref "backend/" >}}). Because backend plugins run on the server, they can access decrypted secrets, which makes it easier to implement custom authentication methods.
The decrypted secrets are available from the `DecryptedSecureJSONData` field in the instance settings.
@ -285,9 +302,9 @@ func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataReque
## Forward OAuth identity for the logged-in user
If your data source uses the same OAuth provider as Grafana itself, for example using [Generic OAuth Authentication]({{< relref "../../setup-grafana/configure-security/configure-authentication/generic-oauth/" >}}), your data source plugin can reuse the access token for the logged-in Grafana user.
If your data source uses the same OAuth provider as Grafana itself, for example using [Generic OAuth Authentication]({{< relref "../../setup-grafana/configure-security/configure-authentication/generic-oauth/" >}}), then your data source plugin can reuse the access token for the logged-in Grafana user.
To allow Grafana to pass the access token to the plugin, update the data source configuration and set the` jsonData.oauthPassThru` property to `true`. The [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) provides a toggle, the **Forward OAuth Identity** option, for this. You can also build an appropriate toggle to set `jsonData.oauthPassThru` in your data source configuration page UI.
To allow Grafana to pass the access token to the plugin, update the data source configuration and set the `jsonData.oauthPassThru` property to `true`. The [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) settings provide a toggle, the **Forward OAuth Identity** option, for this. You can also build an appropriate toggle to set `jsonData.oauthPassThru` in your data source configuration page UI.
When configured, Grafana can forward authorization HTTP headers such as `Authorization` or `X-ID-Token` to a backend data source. This information is available across the `QueryData`, `CallResource` and `CheckHealth` requests.
@ -295,14 +312,15 @@ To get Grafana to forward the headers, create a HTTP client using the [Grafana p
```go
func NewDatasource(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
opts, err := settings.HTTPClientOptions()
opts, err := settings.HTTPClientOptions()
if err != nil {
return nil, fmt.Errorf("http client options: %w", err)
}
// Important: Reuse the same client for each query to avoid using all available connections on a host.
opts.ForwardHTTPHeaders = true
// Important to reuse the same client for each query, to avoid using all available connections on a host
cl, err := httpclient.New(opts)
if err != nil {
return nil, fmt.Errorf("httpclient new: %w", err)
@ -313,7 +331,7 @@ func NewDatasource(settings backend.DataSourceInstanceSettings) (instancemgmt.In
}
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
// Important to keep the Context, since the injected middleware is configured there
// Necessary to keep the Context, since the injected middleware is configured there
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://some-url", nil)
if err != nil {
return nil, fmt.Errorf("new request with context: %w", err)
@ -324,9 +342,11 @@ func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataReque
}
```
You can see a full working example here: [datasource-http-backend](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-http-backend).
You can see a full working plugin example here: [datasource-http-backend](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-http-backend).
If you need to access HTTP header information directory, you can also extract that information from the request:
### Extract a header from an HTTP request
If you need to access the HTTP header information directly, you can also extract that information from the request:
```go
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
@ -362,27 +382,41 @@ func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourc
}
```
## Forward cookies for the logged-in user
## Work with cookies
### Forward cookies for the logged-in user
Your data source plugin can forward cookies for the logged-in Grafana user to the data source. Use the [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) component on the data source's configuration page. It provides the **Allowed cookies** option, where you can specify the cookie names.
When configured, as with [authorization headers](#forward-oauth-identity-for-the-logged-in-user), these cookies are automatically injected if the SDK HTTP client is used.
When configured, as with [authorization headers](#forward-oauth-identity-for-the-logged-in-user), these cookies are automatically injected if you use the SDK HTTP client.
### Extract cookies for the logged-in user
You can also extract the cookies in the `QueryData`, `CallResource` and `CheckHealth` requests if required.
**`QueryData`**
```go
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
// ...
}
```
**`CallResource`**
```go
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
// ...
}
```
**`CheckHealth`**
```go
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
@ -392,7 +426,9 @@ func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthR
## Forward user header for the logged-in user
When [send_user_header]({{< relref "../../setup-grafana/configure-grafana/_index.md#send_user_header" >}}) is enabled, Grafana will pass the user header to the plugin using the `X-Grafana-User` header. You can forward this header as well as [authorization headers](#forward-oauth-identity-for-the-logged-in-user) or [configured cookies](#forward-cookies-for-the-logged-in-user).
When [send_user_header]({{< relref "../../setup-grafana/configure-grafana/_index.md#send_user_header" >}}) is enabled, Grafana passes the user header to the plugin using the `X-Grafana-User` header. You can forward this header as well as [authorization headers](#forward-oauth-identity-for-the-logged-in-user) or [configured cookies](#forward-cookies-for-the-logged-in-user).
**`QueryData`**
```go
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
@ -400,13 +436,21 @@ func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataReque
// ...
}
```
**`CallResource`**
```go
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
u := req.GetHTTPHeader("X-Grafana-User")
// ...
}
```
**`CheckHealth`**
```go
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
u := req.GetHTTPHeader("X-Grafana-User")