包阅导读总结
1.
关键词:Grafana、app plugin、third-party APIs、authentication、secure
2.
总结:本文介绍了在开发 Grafana 应用插件时如何安全地与第三方 API 进行认证,包括为何需要安全认证、相关安全措施、处理 API 密钥、创建资源端点及从前端调用端点等步骤,以确保敏感信息安全。
3.
主要内容:
– 介绍 Grafana 应用插件可增强体验,数据来源多样,第三方 API 通常需认证
– 强调与第三方 API 集成时安全认证的重要性及风险
– 列举如凭证泄漏、中间人攻击等风险
– 阐述 Grafana 应用插件中的安全 API 认证
– 介绍确保安全处理凭证和不暴露敏感信息的最佳实践
– 包括使用 HTTPS 保证传输安全、用 Grafana 的 SecureJsonData 插件配置存储数据
– 利用 Resources 功能最小化可见范围
– 说明处理 API 密钥的方法
– Grafana 提供机制通过插件配置存储敏感信息
– 通过特定函数保存用户配置,区分普通和安全数据存储
– 可在开发实例中更新配置或通过文件设置默认值
– 讲解创建调用第三方 API 的 Resources 端点
– 在特定文件中配置端点
– 实现处理端点的函数,获取配置并进行认证请求
– 重建后端代码和重启容器使端点生效
– 提及从前端调用 Resources 端点的方法
– 使用特定函数和包进行调用
– 获取响应值并输出
思维导图:
文章来源:grafana.com
作者:Tom Glenn
发布时间:2024/7/26 9:21
语言:英文
总字数:1394字
预计阅读时间:6分钟
评分:89分
标签:Grafana,API 认证,插件开发,安全最佳实践,后端数据源
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
Whether they’re for synthetic monitoring, large-language models, or some other use case, Grafana application plugins are a fantastic way to enhance your overall Grafana experience. Data for these custom experiences can come from a variety of sources, including nested data sources. However, they can also come from third-party APIs, which usually require authentication to access.
This blog post explains, step by step, how to securely authenticate against third-party APIs when developing your Grafana app plugin. By the end, you’ll understand how to handle authentication securely, ensuring that sensitive credentials are protected throughout interactions with third-party APIs.
Note: For those new to Grafana, it might be helpful to understand the distinctions between our panel plugins, data source plugins, and application plugins before continuing with this post. Each plugin type serves a unique purpose and has different capabilities within the Grafana ecosystem. You can find a detailed overview of each type in our developer documentation.
The need for secure authentication with third-party APIs
When integrating with third-party APIs, it’s crucial to secure your connection and use authentication credentials to protect sensitive data from unauthorized access. The mishandling of authentication can expose you to several risks, including credential leaks, man-in-the-middle attacks, replay attacks, impersonation, and compliance violations. This could happen, for example, if you simply store your API authentication credentials in plain text, directly inside your frontend components, and then use them to call a third-party API.
To avoid these risks, it’s essential to implement robust security measures for API credentials and ensure encrypted communications with third-party services.
Secure API authentication in Grafana app plugins
While the following steps will help you securely authenticate against a third-party API within a Grafan app plugin, they can also be used within a backend data source plugin.
There are several best practices to ensure credentials are handled safely and that plugins do not expose sensitive information, either in transit or at rest:
- In transit: Ensure you’re using HTTPS at all times to guarantee secure communication.
- At rest: Use Grafana’s SecureJsonData plugin configuration to securely store sensitive data like API keys.
- Minimize scope of visibility: Use the Resources feature in the Grafana plugin architecture to act as an intermediary for requests to third-party APIs. This lets you securely fetch data in the frontend without exposing credentials to the client.
Handling API keys
Grafana provides a mechanism to store sensitive information, such as API keys or passwords, using secure JSON data fields within the plugin’s configuration. These credentials are encrypted and can only be accessed server-side, ensuring they are not exposed in the browser.
When bootstrapping your Grafana app plugin, using the Create Plugin CLI tool, your app plugin source code will contain a configuration page with an example for storing secure credentials. This can be found in the src/components/AppConfig/AppConfig.tsx
file.
You will see that there are two pieces of data being stored as part of the plugin’s configuration: apiUrl
and apiKey
.
Inside of the configuration page’s source code, you’ll notice the Submit button has an onClick
handler that calls the updatePluginAndReload
function as shown below:
updatePluginAndReload(plugin.meta.id, { enabled, pinned, jsonData: { apiUrl: state.apiUrl, }, secureJsonData: state.isApiKeySet ? undefined : { apiKey: state.apiKey, }, })
This call to the updatePluginAndReload
function saves the user’s configuration form input in the plugin’s configuration settings. The apiUrl
is being stored as plain JSON data, while the apiKey
is being stored as secure JSON data. This means the frontend of your app plugin has no way to retrieve the raw value of the apiKey
which can therefore only be used by the backend part of your plugin. This ensures that your API credentials remain private and secure.
Remember to run your development Grafana instance and navigate to your app plugin’s configuration page to update the configuration appropriately with your third-party API credentials.
Alternatively, for development purposes, you may want to enter default values for your plugin’s configuration via the provisioning/plugins/app.yaml
file, as shown below. This will default your plugin’s configuration to the values entered in this file each time the Docker containers are restarted.
As mentioned earlier, it is incredibly important that you use HTTPS to create a secure connection to your API.
Creating a Resources endpoint to call the third-party API
Grafana plugins can use the Resources functionality to allow the frontend to retrieve arbitrary data from the plugin’s backend. What you decide to expose via the Resources endpoints is entirely up to you as the developer, but in this instance, we will use a Resources endpoint to retrieve data from an authenticated third-party API.
Resources endpoints within your app plugin are configured inside the pkg/plugin/resources.go
file. Inside this file there is a registerRoutes
function that defines which Resources endpoints are available within your app plugin.
To register a new endpoint, define it within the registerRoutes
function, as shown below:
func (a *App) registerRoutes(mux *http.ServeMux) { mux.HandleFunc("/ping", a.handlePing) mux.HandleFunc("/echo", a.handleEcho) mux.HandleFunc("/my-new-endpoint", a.handleMyNewEndpoint) // Newly registered endpoint}
Above we have defined a new Resources endpoint that can be accessed at the Resources location for our plugin by the frontend – for example, /api/plugins/<your-plugin-id>/resources/my-new-endpoint
.
This new endpoint is configured to be handled by a new function, a.handleMyNewEndpoint
. We must implement this function, which is a standard Go HTTP handler, in order to return data to the frontend when the endpoint is called.
Let’s take a look at an example implementation that gets the API URL and API key from your plugin configuration, and then makes an authenticated HTTP request to the third-party API before returning the data to the client.
func (a *App) handleMyNewEndpoint(w http.ResponseWriter, req *http.Request) { // Only allow HTTP GET calls if req.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } // Get the Plugin context pCtx := httpadapter.PluginConfigFromContext(req.Context()) // Get the API Key from the plugin’s secure JSON configuration data apiKey, exists := pCtx.AppInstanceSettings.DecryptedSecureJSONData["apiKey"] if !exists { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Get the API URL from the plugin’s standard JSON configuration data var config pluginConfig if err := json.Unmarshal(pCtx.AppInstanceSettings.JSONData, &config); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if config.ApiUrl == "" { return nil, errors.New("no api url configured") } // Define the full API endpoint to call (in this instance the /profile endpoint of the third-party API) apiUrl := fmt.Sprintf("%s/profile", config.ApiUrl) // Create a new HTTP request to the API client := &http.Client{} apiReq, err := http.NewRequest("GET", apiUrl, nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Set the Authorization header using the API Key apiReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) // Make the request apiResp, err := client.Do(apiReq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer apiResp.Body.Close() // Read the response body body, err := io.ReadAll(apiResp.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Write the response body as JSON for the caller to consume w.Header().Set("Content-Type", "application/json") w.Write(body)}
The above code uses the plugin’s configuration to call out to the authenticated third-party API’s /profile
endpoint and return the data to the client.
Remember, in order for this new Resources endpoint to take effect, the backend source code of the plugin must be rebuilt using the mage -v build:linux
command and then the Docker containers must be restarted using Docker Compose.
Calling the Resources endpoint from the frontend
Your plugin’s frontend can now use the newly created Resources endpoint to retrieve data from the authenticated third-party API.
To call the Resources endpoint, use the getBackendSrv().fetch()
function, which is part of the @grafana/runtime
package. We also use the lastValueFrom
function, which is available as part of the rxjs
package.
An example of this is shown below:
const response = getBackendSrv().fetch({ url: 'api/plugins/myorg-myplugin-app/resources/my-new-endpoint});const value = await lastValueFrom(response) as any;console.log(value);
The above code will make a call to the new Resources endpoint, grab the value from the response, and output it to the browser’s console.
How to learn more
By following the steps above, you can securely authenticate against third-party APIs within your Grafana app plugin, ensuring that your integrations are not only powerful, but secure. This method protects sensitive credentials and maintains the integrity and security of your data flows.
For more information on Grafana plugin development and the Resources API, refer to the Grafana developer portal. Additionally, you can explore our community forums to learn best practices and get support from other Grafana developers.