grafana/pkg/api/datasources.go

1058 lines
33 KiB
Go
Raw Normal View History

package api
import (
Encryption: Refactor securejsondata.SecureJsonData to stop relying on global functions (#38865) * Encryption: Add support to encrypt/decrypt sjd * Add datasources.Service as a proxy to datasources db operations * Encrypt ds.SecureJsonData before calling SQLStore * Move ds cache code into ds service * Fix tlsmanager tests * Fix pluginproxy tests * Remove some securejsondata.GetEncryptedJsonData usages * Add pluginsettings.Service as a proxy for plugin settings db operations * Add AlertNotificationService as a proxy for alert notification db operations * Remove some securejsondata.GetEncryptedJsonData usages * Remove more securejsondata.GetEncryptedJsonData usages * Fix lint errors * Minor fixes * Remove encryption global functions usages from ngalert * Fix lint errors * Minor fixes * Minor fixes * Remove securejsondata.DecryptedValue usage * Refactor the refactor * Remove securejsondata.DecryptedValue usage * Move securejsondata to migrations package * Move securejsondata to migrations package * Minor fix * Fix integration test * Fix integration tests * Undo undesired changes * Fix tests * Add context.Context into encryption methods * Fix tests * Fix tests * Fix tests * Trigger CI * Fix test * Add names to params of encryption service interface * Remove bus from CacheServiceImpl * Add logging * Add keys to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Add missing key to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Undo changes in markdown files * Fix formatting * Add context to secrets service * Rename decryptSecureJsonData to decryptSecureJsonDataFn * Name args in GetDecryptedValueFn * Add template back to NewAlertmanagerNotifier * Copy GetDecryptedValueFn to ngalert * Add logging to pluginsettings * Fix pluginsettings test Co-authored-by: Tania B <yalyna.ts@gmail.com> Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
2021-10-07 09:33:50 -05:00
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
"strconv"
2022-09-20 12:31:08 -05:00
"strings"
Encryption: Refactor securejsondata.SecureJsonData to stop relying on global functions (#38865) * Encryption: Add support to encrypt/decrypt sjd * Add datasources.Service as a proxy to datasources db operations * Encrypt ds.SecureJsonData before calling SQLStore * Move ds cache code into ds service * Fix tlsmanager tests * Fix pluginproxy tests * Remove some securejsondata.GetEncryptedJsonData usages * Add pluginsettings.Service as a proxy for plugin settings db operations * Add AlertNotificationService as a proxy for alert notification db operations * Remove some securejsondata.GetEncryptedJsonData usages * Remove more securejsondata.GetEncryptedJsonData usages * Fix lint errors * Minor fixes * Remove encryption global functions usages from ngalert * Fix lint errors * Minor fixes * Minor fixes * Remove securejsondata.DecryptedValue usage * Refactor the refactor * Remove securejsondata.DecryptedValue usage * Move securejsondata to migrations package * Move securejsondata to migrations package * Minor fix * Fix integration test * Fix integration tests * Undo undesired changes * Fix tests * Add context.Context into encryption methods * Fix tests * Fix tests * Fix tests * Trigger CI * Fix test * Add names to params of encryption service interface * Remove bus from CacheServiceImpl * Add logging * Add keys to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Add missing key to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Undo changes in markdown files * Fix formatting * Add context to secrets service * Rename decryptSecureJsonData to decryptSecureJsonDataFn * Name args in GetDecryptedValueFn * Add template back to NewAlertmanagerNotifier * Copy GetDecryptedValueFn to ngalert * Add logging to pluginsettings * Fix pluginsettings test Co-authored-by: Tania B <yalyna.ts@gmail.com> Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
2021-10-07 09:33:50 -05:00
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/datasource"
2015-02-05 03:37:13 -06:00
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
2022-09-20 12:31:08 -05:00
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/adapters"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/datasources/permissions"
"github.com/grafana/grafana/pkg/services/user"
2022-09-20 12:31:08 -05:00
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/proxyutil"
"github.com/grafana/grafana/pkg/web"
)
var datasourcesLogger = log.New("datasources")
var secretsPluginError datasources.ErrDatasourceSecretsPluginUserFriendly
// swagger:route GET /datasources datasources getDataSources
//
// Get all data sources.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled
// you need to have a permission with action: `datasources:read` and scope: `datasources:*`.
//
// Responses:
// 200: getDataSourcesResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (hs *HTTPServer) GetDataSources(c *models.ReqContext) response.Response {
query := datasources.GetDataSourcesQuery{OrgId: c.OrgID, DataSourceLimit: hs.Cfg.DataSourceLimit}
if err := hs.DataSourcesService.GetDataSources(c.Req.Context(), &query); err != nil {
return response.Error(500, "Failed to query datasources", err)
}
filtered, err := hs.filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, query.Result)
if err != nil {
return response.Error(500, "Failed to query datasources", err)
}
result := make(dtos.DataSourceList, 0)
for _, ds := range filtered {
dsItem := dtos.DataSourceListItemDTO{
OrgId: ds.OrgId,
2018-10-01 08:38:55 -05:00
Id: ds.Id,
UID: ds.Uid,
Name: ds.Name,
Url: ds.Url,
Type: ds.Type,
TypeName: ds.Type,
Access: ds.Access,
Database: ds.Database,
User: ds.User,
BasicAuth: ds.BasicAuth,
2015-01-09 09:36:23 -06:00
IsDefault: ds.IsDefault,
JsonData: ds.JsonData,
ReadOnly: ds.ReadOnly,
}
if plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), ds.Type); exists {
dsItem.TypeLogoUrl = plugin.Info.Logos.Small
dsItem.TypeName = plugin.Name
} else {
2016-03-23 09:09:48 -05:00
dsItem.TypeLogoUrl = "public/img/icn-datasource.svg"
}
result = append(result, dsItem)
}
sort.Sort(result)
2022-04-15 07:01:58 -05:00
return response.JSON(http.StatusOK, &result)
}
// swagger:route GET /datasources/{id} datasources getDataSourceByID
//
// Get a single data source by Id.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled
// you need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).
//
// Please refer to [updated API](#/datasources/getDataSourceByUID) instead
//
// Deprecated: true
//
// Responses:
// 200: getDataSourceResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) GetDataSourceById(c *models.ReqContext) response.Response {
id, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "id is invalid", nil)
}
query := datasources.GetDataSourceQuery{
Id: id,
OrgId: c.OrgID,
}
if err := hs.DataSourcesService.GetDataSource(c.Req.Context(), &query); err != nil {
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
}
if errors.Is(err, datasources.ErrDataSourceIdentifierNotSet) {
return response.Error(400, "Datasource id is missing", nil)
}
return response.Error(500, "Failed to query datasources", err)
}
dto := hs.convertModelToDtos(c.Req.Context(), query.Result)
// Add accesscontrol metadata
dto.AccessControl = hs.getAccessControlMetadata(c, c.OrgID, datasources.ScopePrefix, dto.UID)
2022-04-15 07:01:58 -05:00
return response.JSON(http.StatusOK, &dto)
}
// swagger:route DELETE /datasources/{id} datasources deleteDataSourceByID
//
// Delete an existing data source by id.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled
// you need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).
//
// Please refer to [updated API](#/datasources/deleteDataSourceByUID) instead
//
// Deprecated: true
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 404: notFoundError
// 403: forbiddenError
// 500: internalServerError
func (hs *HTTPServer) DeleteDataSourceById(c *models.ReqContext) response.Response {
id, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "id is invalid", err)
}
if id <= 0 {
return response.Error(400, "Missing valid datasource id", nil)
}
ds, err := hs.getRawDataSourceById(c.Req.Context(), id, c.OrgID)
if err != nil {
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
}
return response.Error(400, "Failed to delete datasource", nil)
}
if ds.ReadOnly {
return response.Error(403, "Cannot delete read-only data source", nil)
}
cmd := &datasources.DeleteDataSourceCommand{ID: id, OrgID: c.OrgID, Name: ds.Name}
err = hs.DataSourcesService.DeleteDataSource(c.Req.Context(), cmd)
if err != nil {
Secrets: add better error handling for secret plugin failures when updating datasources (#50542) * Add protobuf config and generated code, and client wrapper * wire up loading of secretsmanager plugin, using renderer plugin as a model * update kvstore provider to check if we should use the grpc plugin. return false always in OSS * add OSS remote plugin check * refactor wire gen file * log which secrets manager is being used * Fix argument types for remote checker * Turns out if err != nil, then the result is always nil. Return empty values if there is an error. * remove duplicate import * ensure atomicity by adding secret management as a step to sql operations and rolling back if necessary * Update pkg/services/secrets/kvstore/kvstore.go Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * Update pkg/services/secrets/kvstore/kvstore.go Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * refactor RemotePluginCheck interface to just return the Plugin client directly * rename struct to something less silly * add special error handling for remote secrets management * switch to errors.as instead of type inference * remove unnecessary rollback call * just declare error once * refactor .proto file according to prior PR suggestions * re-generate protobuf files and fix compilation errors * only wrap (ergo display in the front end) errors that are user friendly from the plugin * rename error type to suggest user friendly only * rename plugin functions to be more descriptive * change delete message name * Revert "change delete message name" This reverts commit 8ca978301eea45d3cc6a22cda15b9d099c533955. * Revert "rename plugin functions to be more descriptive" This reverts commit 4355c9b9ff95443f74c0b588d1ffeabb544f1a34. * fix pointer to pointer problem * change plugin user error to just hold a string * fix sequencing problem with datasource updates * clean up some return statements * need to wrap multiple transactions with the InTransaction() func in order to keep the lock * make linter happy * revert input var name Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
2022-06-16 11:26:57 -05:00
if errors.As(err, &secretsPluginError) {
return response.Error(500, "Failed to delete datasource: "+err.Error(), err)
}
return response.Error(500, "Failed to delete datasource", err)
}
hs.Live.HandleDatasourceDelete(c.OrgID, ds.Uid)
return response.Success("Data source deleted")
}
// swagger:route GET /datasources/uid/{uid} datasources getDataSourceByUID
//
// Get a single data source by UID.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled
// you need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:kLtEtcRGk` (single data source).
//
// Responses:
// 200: getDataSourceResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) GetDataSourceByUID(c *models.ReqContext) response.Response {
ds, err := hs.getRawDataSourceByUID(c.Req.Context(), web.Params(c.Req)[":uid"], c.OrgID)
if err != nil {
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return response.Error(http.StatusNotFound, "Data source not found", nil)
}
return response.Error(http.StatusInternalServerError, "Failed to query datasource", err)
}
dto := hs.convertModelToDtos(c.Req.Context(), ds)
// Add accesscontrol metadata
dto.AccessControl = hs.getAccessControlMetadata(c, c.OrgID, datasources.ScopePrefix, dto.UID)
2022-04-15 07:01:58 -05:00
return response.JSON(http.StatusOK, &dto)
}
// swagger:route DELETE /datasources/uid/{uid} datasources deleteDataSourceByUID
//
// Delete an existing data source by UID.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled
// you need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:kLtEtcRGk` (single data source).
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) DeleteDataSourceByUID(c *models.ReqContext) response.Response {
uid := web.Params(c.Req)[":uid"]
if uid == "" {
return response.Error(400, "Missing datasource uid", nil)
}
ds, err := hs.getRawDataSourceByUID(c.Req.Context(), uid, c.OrgID)
if err != nil {
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
}
return response.Error(400, "Failed to delete datasource", nil)
}
if ds.ReadOnly {
return response.Error(403, "Cannot delete read-only data source", nil)
}
cmd := &datasources.DeleteDataSourceCommand{UID: uid, OrgID: c.OrgID, Name: ds.Name}
err = hs.DataSourcesService.DeleteDataSource(c.Req.Context(), cmd)
if err != nil {
Secrets: add better error handling for secret plugin failures when updating datasources (#50542) * Add protobuf config and generated code, and client wrapper * wire up loading of secretsmanager plugin, using renderer plugin as a model * update kvstore provider to check if we should use the grpc plugin. return false always in OSS * add OSS remote plugin check * refactor wire gen file * log which secrets manager is being used * Fix argument types for remote checker * Turns out if err != nil, then the result is always nil. Return empty values if there is an error. * remove duplicate import * ensure atomicity by adding secret management as a step to sql operations and rolling back if necessary * Update pkg/services/secrets/kvstore/kvstore.go Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * Update pkg/services/secrets/kvstore/kvstore.go Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * refactor RemotePluginCheck interface to just return the Plugin client directly * rename struct to something less silly * add special error handling for remote secrets management * switch to errors.as instead of type inference * remove unnecessary rollback call * just declare error once * refactor .proto file according to prior PR suggestions * re-generate protobuf files and fix compilation errors * only wrap (ergo display in the front end) errors that are user friendly from the plugin * rename error type to suggest user friendly only * rename plugin functions to be more descriptive * change delete message name * Revert "change delete message name" This reverts commit 8ca978301eea45d3cc6a22cda15b9d099c533955. * Revert "rename plugin functions to be more descriptive" This reverts commit 4355c9b9ff95443f74c0b588d1ffeabb544f1a34. * fix pointer to pointer problem * change plugin user error to just hold a string * fix sequencing problem with datasource updates * clean up some return statements * need to wrap multiple transactions with the InTransaction() func in order to keep the lock * make linter happy * revert input var name Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
2022-06-16 11:26:57 -05:00
if errors.As(err, &secretsPluginError) {
return response.Error(500, "Failed to delete datasource: "+err.Error(), err)
}
return response.Error(500, "Failed to delete datasource", err)
}
hs.Live.HandleDatasourceDelete(c.OrgID, ds.Uid)
2022-04-15 07:01:58 -05:00
return response.JSON(http.StatusOK, util.DynMap{
"message": "Data source deleted",
"id": ds.Id,
})
}
// swagger:route DELETE /datasources/name/{name} datasources deleteDataSourceByName
//
// Delete an existing data source by name.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled
// you need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:name:*` and `datasources:name:test_datasource` (single data source).
//
// Responses:
// 200: deleteDataSourceByNameResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) DeleteDataSourceByName(c *models.ReqContext) response.Response {
name := web.Params(c.Req)[":name"]
if name == "" {
return response.Error(400, "Missing valid datasource name", nil)
}
getCmd := &datasources.GetDataSourceQuery{Name: name, OrgId: c.OrgID}
if err := hs.DataSourcesService.GetDataSource(c.Req.Context(), getCmd); err != nil {
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
}
return response.Error(500, "Failed to delete datasource", err)
}
if getCmd.Result.ReadOnly {
return response.Error(403, "Cannot delete read-only data source", nil)
}
cmd := &datasources.DeleteDataSourceCommand{Name: name, OrgID: c.OrgID}
err := hs.DataSourcesService.DeleteDataSource(c.Req.Context(), cmd)
if err != nil {
Secrets: add better error handling for secret plugin failures when updating datasources (#50542) * Add protobuf config and generated code, and client wrapper * wire up loading of secretsmanager plugin, using renderer plugin as a model * update kvstore provider to check if we should use the grpc plugin. return false always in OSS * add OSS remote plugin check * refactor wire gen file * log which secrets manager is being used * Fix argument types for remote checker * Turns out if err != nil, then the result is always nil. Return empty values if there is an error. * remove duplicate import * ensure atomicity by adding secret management as a step to sql operations and rolling back if necessary * Update pkg/services/secrets/kvstore/kvstore.go Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * Update pkg/services/secrets/kvstore/kvstore.go Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * refactor RemotePluginCheck interface to just return the Plugin client directly * rename struct to something less silly * add special error handling for remote secrets management * switch to errors.as instead of type inference * remove unnecessary rollback call * just declare error once * refactor .proto file according to prior PR suggestions * re-generate protobuf files and fix compilation errors * only wrap (ergo display in the front end) errors that are user friendly from the plugin * rename error type to suggest user friendly only * rename plugin functions to be more descriptive * change delete message name * Revert "change delete message name" This reverts commit 8ca978301eea45d3cc6a22cda15b9d099c533955. * Revert "rename plugin functions to be more descriptive" This reverts commit 4355c9b9ff95443f74c0b588d1ffeabb544f1a34. * fix pointer to pointer problem * change plugin user error to just hold a string * fix sequencing problem with datasource updates * clean up some return statements * need to wrap multiple transactions with the InTransaction() func in order to keep the lock * make linter happy * revert input var name Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
2022-06-16 11:26:57 -05:00
if errors.As(err, &secretsPluginError) {
return response.Error(500, "Failed to delete datasource: "+err.Error(), err)
}
return response.Error(500, "Failed to delete datasource", err)
}
hs.Live.HandleDatasourceDelete(c.OrgID, getCmd.Result.Uid)
2022-04-15 07:01:58 -05:00
return response.JSON(http.StatusOK, util.DynMap{
2020-07-31 01:22:09 -05:00
"message": "Data source deleted",
"id": getCmd.Result.Id,
})
}
func validateURL(cmdType string, url string) response.Response {
if _, err := datasource.ValidateURL(cmdType, url); err != nil {
datasourcesLogger.Error("Failed to validate URL", "url", url)
return response.Error(http.StatusBadRequest, "Validation error, invalid URL", err)
}
return nil
}
2022-09-20 12:31:08 -05:00
// validateJSONData prevents the user from adding a custom header with name that matches the auth proxy header name.
// This is done to prevent data source proxy from being used to circumvent auth proxy.
// For more context take a look at CVE-2022-35957
func validateJSONData(jsonData *simplejson.Json, cfg *setting.Cfg) error {
if jsonData == nil || !cfg.AuthProxyEnabled {
return nil
}
for key, value := range jsonData.MustMap() {
if strings.HasPrefix(key, "httpHeaderName") {
header := fmt.Sprint(value)
if http.CanonicalHeaderKey(header) == http.CanonicalHeaderKey(cfg.AuthProxyHeaderName) {
datasourcesLogger.Error("Forbidden to add a data source header with a name equal to auth proxy header name", "headerName", key)
return errors.New("validation error, invalid header name specified")
}
}
}
return nil
}
// swagger:route POST /datasources datasources addDataSource
//
// Create a data source.
//
// By defining `password` and `basicAuthPassword` under secureJsonData property
// Grafana encrypts them securely as an encrypted blob in the database.
// The response then lists the encrypted fields under secureJsonFields.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled
// you need to have a permission with action: `datasources:create`
//
// Responses:
// 200: createOrUpdateDatasourceResponse
// 401: unauthorisedError
// 403: forbiddenError
// 409: conflictError
// 500: internalServerError
func (hs *HTTPServer) AddDataSource(c *models.ReqContext) response.Response {
cmd := datasources.AddDataSourceCommand{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
datasourcesLogger.Debug("Received command to add data source", "url", cmd.Url)
cmd.OrgId = c.OrgID
cmd.UserId = c.UserID
if cmd.Url != "" {
if resp := validateURL(cmd.Type, cmd.Url); resp != nil {
return resp
}
}
2022-09-20 12:31:08 -05:00
if err := validateJSONData(cmd.JsonData, hs.Cfg); err != nil {
return response.Error(http.StatusBadRequest, "Failed to add datasource", err)
}
if err := hs.DataSourcesService.AddDataSource(c.Req.Context(), &cmd); err != nil {
if errors.Is(err, datasources.ErrDataSourceNameExists) || errors.Is(err, datasources.ErrDataSourceUidExists) {
return response.Error(409, err.Error(), err)
}
Secrets: add better error handling for secret plugin failures when updating datasources (#50542) * Add protobuf config and generated code, and client wrapper * wire up loading of secretsmanager plugin, using renderer plugin as a model * update kvstore provider to check if we should use the grpc plugin. return false always in OSS * add OSS remote plugin check * refactor wire gen file * log which secrets manager is being used * Fix argument types for remote checker * Turns out if err != nil, then the result is always nil. Return empty values if there is an error. * remove duplicate import * ensure atomicity by adding secret management as a step to sql operations and rolling back if necessary * Update pkg/services/secrets/kvstore/kvstore.go Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * Update pkg/services/secrets/kvstore/kvstore.go Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * refactor RemotePluginCheck interface to just return the Plugin client directly * rename struct to something less silly * add special error handling for remote secrets management * switch to errors.as instead of type inference * remove unnecessary rollback call * just declare error once * refactor .proto file according to prior PR suggestions * re-generate protobuf files and fix compilation errors * only wrap (ergo display in the front end) errors that are user friendly from the plugin * rename error type to suggest user friendly only * rename plugin functions to be more descriptive * change delete message name * Revert "change delete message name" This reverts commit 8ca978301eea45d3cc6a22cda15b9d099c533955. * Revert "rename plugin functions to be more descriptive" This reverts commit 4355c9b9ff95443f74c0b588d1ffeabb544f1a34. * fix pointer to pointer problem * change plugin user error to just hold a string * fix sequencing problem with datasource updates * clean up some return statements * need to wrap multiple transactions with the InTransaction() func in order to keep the lock * make linter happy * revert input var name Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
2022-06-16 11:26:57 -05:00
if errors.As(err, &secretsPluginError) {
return response.Error(500, "Failed to add datasource: "+err.Error(), err)
}
return response.Error(500, "Failed to add datasource", err)
}
Secrets: Implement basic unified secret store service (#45804) * wip: Implement kvstore for secrets * wip: Refactor kvstore for secrets * wip: Add format key function to secrets kvstore sql * wip: Add migration for secrets kvstore * Remove unused Key field from secrets kvstore * Remove secret values from debug logs * Integrate unified secrets with datasources * Fix minor issues and tests for kvstore * Create test service helper for secret store * Remove encryption tests from datasources * Move secret operations after datasources * Fix datasource proxy tests * Fix legacy data tests * Add Name to all delete data source commands * Implement decryption cache on sql secret store * Fix minor issue with cache and tests * Use secret type on secret store datasource operations * Add comments to make create and update clear * Rename itemFound variable to isFound * Improve secret deletion and cache management * Add base64 encoding to sql secret store * Move secret retrieval to decrypted values function * Refactor decrypt secure json data functions * Fix expr tests * Fix datasource tests * Fix plugin proxy tests * Fix query tests * Fix metrics api tests * Remove unused fake secrets service from query tests * Add rename function to secret store * Add check for error renaming secret * Remove bus from tests to fix merge conflicts * Add background secrets migration to datasources * Get datasource secure json fields from secrets * Move migration to secret store * Revert "Move migration to secret store" This reverts commit 7c3f872072e9aff601fb9d639127d468c03f97ef. * Add secret service to datasource service on tests * Fix datasource tests * Remove merge conflict on wire * Add ctx to data source http transport on prometheus stats collector * Add ctx to data source http transport on stats collector test
2022-04-25 11:57:45 -05:00
ds := hs.convertModelToDtos(c.Req.Context(), cmd.Result)
2022-04-15 07:01:58 -05:00
return response.JSON(http.StatusOK, util.DynMap{
"message": "Datasource added",
"id": cmd.Result.Id,
"name": cmd.Result.Name,
"datasource": ds,
})
}
// swagger:route PUT /datasources/{id} datasources updateDataSourceByID
//
// Update an existing data source by its sequential ID.
//
// Similar to creating 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.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled
// you need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).
//
// Please refer to [updated API](#/datasources/updateDataSourceByUID) instead
//
// Deprecated: true
//
// Responses:
// 200: createOrUpdateDatasourceResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (hs *HTTPServer) UpdateDataSourceByID(c *models.ReqContext) response.Response {
cmd := datasources.UpdateDataSourceCommand{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
datasourcesLogger.Debug("Received command to update data source", "url", cmd.Url)
cmd.OrgId = c.OrgID
var err error
if cmd.Id, err = strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64); err != nil {
return response.Error(http.StatusBadRequest, "id is invalid", err)
}
if resp := validateURL(cmd.Type, cmd.Url); resp != nil {
return resp
}
2022-09-20 12:31:08 -05:00
if err := validateJSONData(cmd.JsonData, hs.Cfg); err != nil {
return response.Error(http.StatusBadRequest, "Failed to update datasource", err)
}
ds, err := hs.getRawDataSourceById(c.Req.Context(), cmd.Id, cmd.OrgId)
if err != nil {
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
}
return response.Error(500, "Failed to update datasource", err)
}
return hs.updateDataSourceByID(c, ds, cmd)
}
// swagger:route PUT /datasources/uid/{uid} datasources updateDataSourceByUID
//
// Update an existing data source.
//
// Similar to creating 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.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled
// you need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).
//
// Responses:
// 200: createOrUpdateDatasourceResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (hs *HTTPServer) UpdateDataSourceByUID(c *models.ReqContext) response.Response {
cmd := datasources.UpdateDataSourceCommand{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
datasourcesLogger.Debug("Received command to update data source", "url", cmd.Url)
cmd.OrgId = c.OrgID
if resp := validateURL(cmd.Type, cmd.Url); resp != nil {
return resp
}
2022-09-20 12:31:08 -05:00
if err := validateJSONData(cmd.JsonData, hs.Cfg); err != nil {
return response.Error(http.StatusBadRequest, "Failed to update datasource", err)
}
ds, err := hs.getRawDataSourceByUID(c.Req.Context(), web.Params(c.Req)[":uid"], c.OrgID)
if err != nil {
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return response.Error(http.StatusNotFound, "Data source not found", nil)
}
return response.Error(http.StatusInternalServerError, "Failed to update datasource", err)
}
cmd.Id = ds.Id
return hs.updateDataSourceByID(c, ds, cmd)
}
func (hs *HTTPServer) updateDataSourceByID(c *models.ReqContext, ds *datasources.DataSource, cmd datasources.UpdateDataSourceCommand) response.Response {
if ds.ReadOnly {
return response.Error(403, "Cannot update read-only data source", nil)
}
err := hs.DataSourcesService.UpdateDataSource(c.Req.Context(), &cmd)
if err != nil {
if errors.Is(err, datasources.ErrDataSourceUpdatingOldVersion) {
return response.Error(409, "Datasource has already been updated by someone else. Please reload and try again", err)
}
Secrets: add better error handling for secret plugin failures when updating datasources (#50542) * Add protobuf config and generated code, and client wrapper * wire up loading of secretsmanager plugin, using renderer plugin as a model * update kvstore provider to check if we should use the grpc plugin. return false always in OSS * add OSS remote plugin check * refactor wire gen file * log which secrets manager is being used * Fix argument types for remote checker * Turns out if err != nil, then the result is always nil. Return empty values if there is an error. * remove duplicate import * ensure atomicity by adding secret management as a step to sql operations and rolling back if necessary * Update pkg/services/secrets/kvstore/kvstore.go Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * Update pkg/services/secrets/kvstore/kvstore.go Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * refactor RemotePluginCheck interface to just return the Plugin client directly * rename struct to something less silly * add special error handling for remote secrets management * switch to errors.as instead of type inference * remove unnecessary rollback call * just declare error once * refactor .proto file according to prior PR suggestions * re-generate protobuf files and fix compilation errors * only wrap (ergo display in the front end) errors that are user friendly from the plugin * rename error type to suggest user friendly only * rename plugin functions to be more descriptive * change delete message name * Revert "change delete message name" This reverts commit 8ca978301eea45d3cc6a22cda15b9d099c533955. * Revert "rename plugin functions to be more descriptive" This reverts commit 4355c9b9ff95443f74c0b588d1ffeabb544f1a34. * fix pointer to pointer problem * change plugin user error to just hold a string * fix sequencing problem with datasource updates * clean up some return statements * need to wrap multiple transactions with the InTransaction() func in order to keep the lock * make linter happy * revert input var name Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
2022-06-16 11:26:57 -05:00
if errors.As(err, &secretsPluginError) {
return response.Error(500, "Failed to update datasource: "+err.Error(), err)
}
return response.Error(500, "Failed to update datasource", err)
}
query := datasources.GetDataSourceQuery{
Id: cmd.Id,
OrgId: c.OrgID,
}
if err := hs.DataSourcesService.GetDataSource(c.Req.Context(), &query); err != nil {
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
}
return response.Error(500, "Failed to query datasource", err)
}
Secrets: Implement basic unified secret store service (#45804) * wip: Implement kvstore for secrets * wip: Refactor kvstore for secrets * wip: Add format key function to secrets kvstore sql * wip: Add migration for secrets kvstore * Remove unused Key field from secrets kvstore * Remove secret values from debug logs * Integrate unified secrets with datasources * Fix minor issues and tests for kvstore * Create test service helper for secret store * Remove encryption tests from datasources * Move secret operations after datasources * Fix datasource proxy tests * Fix legacy data tests * Add Name to all delete data source commands * Implement decryption cache on sql secret store * Fix minor issue with cache and tests * Use secret type on secret store datasource operations * Add comments to make create and update clear * Rename itemFound variable to isFound * Improve secret deletion and cache management * Add base64 encoding to sql secret store * Move secret retrieval to decrypted values function * Refactor decrypt secure json data functions * Fix expr tests * Fix datasource tests * Fix plugin proxy tests * Fix query tests * Fix metrics api tests * Remove unused fake secrets service from query tests * Add rename function to secret store * Add check for error renaming secret * Remove bus from tests to fix merge conflicts * Add background secrets migration to datasources * Get datasource secure json fields from secrets * Move migration to secret store * Revert "Move migration to secret store" This reverts commit 7c3f872072e9aff601fb9d639127d468c03f97ef. * Add secret service to datasource service on tests * Fix datasource tests * Remove merge conflict on wire * Add ctx to data source http transport on prometheus stats collector * Add ctx to data source http transport on stats collector test
2022-04-25 11:57:45 -05:00
datasourceDTO := hs.convertModelToDtos(c.Req.Context(), query.Result)
hs.Live.HandleDatasourceUpdate(c.OrgID, datasourceDTO.UID)
2022-04-15 07:01:58 -05:00
return response.JSON(http.StatusOK, util.DynMap{
"message": "Datasource updated",
"id": cmd.Id,
"name": cmd.Name,
"datasource": datasourceDTO,
})
}
func (hs *HTTPServer) getRawDataSourceById(ctx context.Context, id int64, orgID int64) (*datasources.DataSource, error) {
query := datasources.GetDataSourceQuery{
Id: id,
2018-03-22 06:37:35 -05:00
OrgId: orgID,
}
if err := hs.DataSourcesService.GetDataSource(ctx, &query); err != nil {
return nil, err
}
return query.Result, nil
}
func (hs *HTTPServer) getRawDataSourceByUID(ctx context.Context, uid string, orgID int64) (*datasources.DataSource, error) {
query := datasources.GetDataSourceQuery{
Uid: uid,
OrgId: orgID,
}
if err := hs.DataSourcesService.GetDataSource(ctx, &query); err != nil {
return nil, err
}
return query.Result, nil
}
// swagger:route GET /datasources/name/{name} datasources getDataSourceByName
//
// Get a single data source by Name.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled
// you need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:name:*` and `datasources:name:test_datasource` (single data source).
//
// Responses:
// 200: getDataSourceResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (hs *HTTPServer) GetDataSourceByName(c *models.ReqContext) response.Response {
query := datasources.GetDataSourceQuery{Name: web.Params(c.Req)[":name"], OrgId: c.OrgID}
2016-03-07 14:25:26 -06:00
if err := hs.DataSourcesService.GetDataSource(c.Req.Context(), &query); err != nil {
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
2016-03-07 14:25:26 -06:00
}
return response.Error(500, "Failed to query datasources", err)
2016-03-07 14:25:26 -06:00
}
dto := hs.convertModelToDtos(c.Req.Context(), query.Result)
2022-04-15 07:01:58 -05:00
return response.JSON(http.StatusOK, &dto)
2016-03-08 04:51:03 -06:00
}
2016-03-07 14:25:26 -06:00
// swagger:route GET /datasources/id/{name} datasources getDataSourceIdByName
//
// Get data source Id by Name.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled
// you need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:name:*` and `datasources:name:test_datasource` (single data source).
//
// Responses:
// 200: getDataSourceIDResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) GetDataSourceIdByName(c *models.ReqContext) response.Response {
query := datasources.GetDataSourceQuery{Name: web.Params(c.Req)[":name"], OrgId: c.OrgID}
2016-03-10 03:31:10 -06:00
if err := hs.DataSourcesService.GetDataSource(c.Req.Context(), &query); err != nil {
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
2016-03-10 03:31:10 -06:00
}
return response.Error(500, "Failed to query datasources", err)
2016-03-10 03:31:10 -06:00
}
ds := query.Result
dtos := dtos.AnyId{
Id: ds.Id,
}
2022-04-15 07:01:58 -05:00
return response.JSON(http.StatusOK, &dtos)
2016-03-10 03:31:10 -06:00
}
// swagger:route GET /datasources/{id}/resources/{datasource_proxy_route} datasources callDatasourceResourceByID
//
// Fetch data source resources by Id.
//
// Please refer to [updated API](#/datasources/callDatasourceResourceWithUID) instead
//
// Deprecated: true
//
// Responses:
// 200: okResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) CallDatasourceResource(c *models.ReqContext) {
datasourceID, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
if err != nil {
c.JsonApiErr(http.StatusBadRequest, "id is invalid", nil)
return
}
ds, err := hs.DataSourceCache.GetDatasource(c.Req.Context(), datasourceID, c.SignedInUser, c.SkipCache)
if err != nil {
if errors.Is(err, datasources.ErrDataSourceAccessDenied) {
c.JsonApiErr(403, "Access denied to datasource", err)
return
}
c.JsonApiErr(500, "Unable to load datasource meta data", err)
return
}
plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), ds.Type)
if !exists {
c.JsonApiErr(500, "Unable to find datasource plugin", err)
return
}
hs.callPluginResourceWithDataSource(c, plugin.ID, ds)
}
// swagger:route GET /datasources/uid/{uid}/resources/{datasource_proxy_route} datasources callDatasourceResourceWithUID
//
// Fetch data source resources.
//
// Responses:
// 200: okResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
func (hs *HTTPServer) CallDatasourceResourceWithUID(c *models.ReqContext) {
dsUID := web.Params(c.Req)[":uid"]
if !util.IsValidShortUID(dsUID) {
c.JsonApiErr(http.StatusBadRequest, "UID is invalid", nil)
return
}
ds, err := hs.DataSourceCache.GetDatasourceByUID(c.Req.Context(), dsUID, c.SignedInUser, c.SkipCache)
if err != nil {
if errors.Is(err, datasources.ErrDataSourceAccessDenied) {
c.JsonApiErr(http.StatusForbidden, "Access denied to datasource", err)
return
}
c.JsonApiErr(http.StatusInternalServerError, "Unable to load datasource meta data", err)
return
}
plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), ds.Type)
if !exists {
c.JsonApiErr(http.StatusInternalServerError, "Unable to find datasource plugin", err)
return
}
hs.callPluginResourceWithDataSource(c, plugin.ID, ds)
}
func (hs *HTTPServer) convertModelToDtos(ctx context.Context, ds *datasources.DataSource) dtos.DataSource {
dto := dtos.DataSource{
Id: ds.Id,
UID: ds.Uid,
OrgId: ds.OrgId,
Name: ds.Name,
Url: ds.Url,
Type: ds.Type,
Access: ds.Access,
Database: ds.Database,
User: ds.User,
BasicAuth: ds.BasicAuth,
BasicAuthUser: ds.BasicAuthUser,
WithCredentials: ds.WithCredentials,
IsDefault: ds.IsDefault,
JsonData: ds.JsonData,
SecureJsonFields: map[string]bool{},
Version: ds.Version,
ReadOnly: ds.ReadOnly,
}
Secrets: Implement basic unified secret store service (#45804) * wip: Implement kvstore for secrets * wip: Refactor kvstore for secrets * wip: Add format key function to secrets kvstore sql * wip: Add migration for secrets kvstore * Remove unused Key field from secrets kvstore * Remove secret values from debug logs * Integrate unified secrets with datasources * Fix minor issues and tests for kvstore * Create test service helper for secret store * Remove encryption tests from datasources * Move secret operations after datasources * Fix datasource proxy tests * Fix legacy data tests * Add Name to all delete data source commands * Implement decryption cache on sql secret store * Fix minor issue with cache and tests * Use secret type on secret store datasource operations * Add comments to make create and update clear * Rename itemFound variable to isFound * Improve secret deletion and cache management * Add base64 encoding to sql secret store * Move secret retrieval to decrypted values function * Refactor decrypt secure json data functions * Fix expr tests * Fix datasource tests * Fix plugin proxy tests * Fix query tests * Fix metrics api tests * Remove unused fake secrets service from query tests * Add rename function to secret store * Add check for error renaming secret * Remove bus from tests to fix merge conflicts * Add background secrets migration to datasources * Get datasource secure json fields from secrets * Move migration to secret store * Revert "Move migration to secret store" This reverts commit 7c3f872072e9aff601fb9d639127d468c03f97ef. * Add secret service to datasource service on tests * Fix datasource tests * Remove merge conflict on wire * Add ctx to data source http transport on prometheus stats collector * Add ctx to data source http transport on stats collector test
2022-04-25 11:57:45 -05:00
secrets, err := hs.DataSourcesService.DecryptedValues(ctx, ds)
if err == nil {
for k, v := range secrets {
if len(v) > 0 {
dto.SecureJsonFields[k] = true
}
}
} else {
datasourcesLogger.Debug("Failed to retrieve datasource secrets to parse secure json fields", "error", err)
}
return dto
2016-03-07 14:25:26 -06:00
}
// swagger:route GET /datasources/uid/{uid}/health datasources checkDatasourceHealthWithUID
//
// Sends a health check request to the plugin datasource identified by the UID.
//
// Responses:
// 200: okResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (hs *HTTPServer) CheckDatasourceHealthWithUID(c *models.ReqContext) response.Response {
dsUID := web.Params(c.Req)[":uid"]
if !util.IsValidShortUID(dsUID) {
return response.Error(http.StatusBadRequest, "UID is invalid", nil)
}
ds, err := hs.DataSourceCache.GetDatasourceByUID(c.Req.Context(), dsUID, c.SignedInUser, c.SkipCache)
if err != nil {
if errors.Is(err, datasources.ErrDataSourceAccessDenied) {
return response.Error(http.StatusForbidden, "Access denied to datasource", err)
}
return response.Error(http.StatusInternalServerError, "Unable to load datasource metadata", err)
}
return hs.checkDatasourceHealth(c, ds)
}
// swagger:route GET /datasources/{id}/health datasources checkDatasourceHealthByID
//
// Sends a health check request to the plugin datasource identified by the ID.
//
// Please refer to [updated API](#/datasources/checkDatasourceHealthWithUID) instead
//
// Deprecated: true
//
// Responses:
// 200: okResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Response {
datasourceID, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
if err != nil {
return response.Error(http.StatusBadRequest, "id is invalid", nil)
}
ds, err := hs.DataSourceCache.GetDatasource(c.Req.Context(), datasourceID, c.SignedInUser, c.SkipCache)
if err != nil {
if errors.Is(err, datasources.ErrDataSourceAccessDenied) {
return response.Error(http.StatusForbidden, "Access denied to datasource", err)
}
return response.Error(http.StatusInternalServerError, "Unable to load datasource metadata", err)
}
return hs.checkDatasourceHealth(c, ds)
}
func (hs *HTTPServer) checkDatasourceHealth(c *models.ReqContext, ds *datasources.DataSource) response.Response {
plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), ds.Type)
if !exists {
return response.Error(http.StatusInternalServerError, "Unable to find datasource plugin", nil)
}
Secrets: Implement basic unified secret store service (#45804) * wip: Implement kvstore for secrets * wip: Refactor kvstore for secrets * wip: Add format key function to secrets kvstore sql * wip: Add migration for secrets kvstore * Remove unused Key field from secrets kvstore * Remove secret values from debug logs * Integrate unified secrets with datasources * Fix minor issues and tests for kvstore * Create test service helper for secret store * Remove encryption tests from datasources * Move secret operations after datasources * Fix datasource proxy tests * Fix legacy data tests * Add Name to all delete data source commands * Implement decryption cache on sql secret store * Fix minor issue with cache and tests * Use secret type on secret store datasource operations * Add comments to make create and update clear * Rename itemFound variable to isFound * Improve secret deletion and cache management * Add base64 encoding to sql secret store * Move secret retrieval to decrypted values function * Refactor decrypt secure json data functions * Fix expr tests * Fix datasource tests * Fix plugin proxy tests * Fix query tests * Fix metrics api tests * Remove unused fake secrets service from query tests * Add rename function to secret store * Add check for error renaming secret * Remove bus from tests to fix merge conflicts * Add background secrets migration to datasources * Get datasource secure json fields from secrets * Move migration to secret store * Revert "Move migration to secret store" This reverts commit 7c3f872072e9aff601fb9d639127d468c03f97ef. * Add secret service to datasource service on tests * Fix datasource tests * Remove merge conflict on wire * Add ctx to data source http transport on prometheus stats collector * Add ctx to data source http transport on stats collector test
2022-04-25 11:57:45 -05:00
dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds, hs.decryptSecureJsonDataFn(c.Req.Context()))
if err != nil {
return response.Error(http.StatusInternalServerError, "Unable to get datasource model", err)
}
Plugins: Refactor Plugin Management (#40477) * add core plugin flow * add instrumentation * move func * remove cruft * support external backend plugins * refactor + clean up * remove comments * refactor loader * simplify core plugin path arg * cleanup loggers * move signature validator to plugins package * fix sig packaging * cleanup plugin model * remove unnecessary plugin field * add start+stop for pm * fix failures * add decommissioned state * export fields just to get things flowing * fix comments * set static routes * make image loading idempotent * merge with backend plugin manager * re-use funcs * reorder imports + remove unnecessary interface * add some TODOs + remove unused func * remove unused instrumentation func * simplify client usage * remove import alias * re-use backendplugin.Plugin interface * re order funcs * improve var name * fix log statements * refactor data model * add logic for dupe check during loading * cleanup state setting * refactor loader * cleanup manager interface * add rendering flow * refactor loading + init * add renderer support * fix renderer plugin * reformat imports * track errors * fix plugin signature inheritance * name param in interface * update func comment * fix func arg name * introduce class concept * remove func * fix external plugin check * apply changes from pm-experiment * fix core plugins * fix imports * rename interface * comment API interface * add support for testdata plugin * enable alerting + use correct core plugin contracts * slim manager API * fix param name * fix filter * support static routes * fix rendering * tidy rendering * get tests compiling * fix install+uninstall * start finder test * add finder test coverage * start loader tests * add test for core plugins * load core + bundled test * add test for nested plugin loading * add test files * clean interface + fix registering some core plugins * refactoring * reformat and create sub packages * simplify core plugin init * fix ctx cancel scenario * migrate initializer * remove Init() funcs * add test starter * new logger * flesh out initializer tests * refactoring * remove unused svc * refactor rendering flow * fixup loader tests * add enabled helper func * fix logger name * fix data fetchers * fix case where plugin dir doesn't exist * improve coverage + move dupe checking to loader * remove noisy debug logs * register core plugins automagically * add support for renderer in catalog * make private func + fix req validation * use interface * re-add check for renderer in catalog * tidy up from moving to auto reg core plugins * core plugin registrar * guards * copy over core plugins for test infra * all tests green * renames * propagate new interfaces * kill old manager * get compiling * tidy up * update naming * refactor manager test + cleanup * add more cases to finder test * migrate validator to field * more coverage * refactor dupe checking * add test for plugin class * add coverage for initializer * split out rendering * move * fixup tests * fix uss test * fix frontend settings * fix grafanads test * add check when checking sig errors * fix enabled map * fixup * allow manual setup of CM * rename to cloud-monitoring * remove TODO * add installer interface for testing * loader interface returns * tests passing * refactor + add more coverage * support 'stackdriver' * fix frontend settings loading * improve naming based on package name * small tidy * refactor test * fix renderer start * make cloud-monitoring plugin ID clearer * add plugin update test * add integration tests * don't break all if sig can't be calculated * add root URL check test * add more signature verification tests * update DTO name * update enabled plugins comment * update comments * fix linter * revert fe naming change * fix errors endpoint * reset error code field name * re-order test to help verify * assert -> require * pm check * add missing entry + re-order * re-check * dump icon log * verify manager contents first * reformat * apply PR feedback * apply style changes * fix one vs all loading err * improve log output * only start when no signature error * move log * rework plugin update check * fix test * fix multi loading from cfg.PluginSettings * improve log output #2 * add error abstraction to capture errors without registering a plugin * add debug log * add unsigned warning * e2e test attempt * fix logger * set home path * prevent panic * alternate * ugh.. fix home path * return renderer even if not started * make renderer plugin managed * add fallback renderer icon, update renderer badge + prevent changes when renderer is installed * fix icon loading * rollback renderer changes * use correct field * remove unneccessary block * remove newline * remove unused func * fix bundled plugins base + module fields * remove unused field since refactor * add authorizer abstraction * loader only returns plugins expected to run * fix multi log output
2021-11-01 04:53:33 -05:00
req := &backend.CheckHealthRequest{
PluginContext: backend.PluginContext{
User: adapters.BackendUserFromSignedInUser(c.SignedInUser),
OrgID: c.OrgID,
Plugins: Refactor Plugin Management (#40477) * add core plugin flow * add instrumentation * move func * remove cruft * support external backend plugins * refactor + clean up * remove comments * refactor loader * simplify core plugin path arg * cleanup loggers * move signature validator to plugins package * fix sig packaging * cleanup plugin model * remove unnecessary plugin field * add start+stop for pm * fix failures * add decommissioned state * export fields just to get things flowing * fix comments * set static routes * make image loading idempotent * merge with backend plugin manager * re-use funcs * reorder imports + remove unnecessary interface * add some TODOs + remove unused func * remove unused instrumentation func * simplify client usage * remove import alias * re-use backendplugin.Plugin interface * re order funcs * improve var name * fix log statements * refactor data model * add logic for dupe check during loading * cleanup state setting * refactor loader * cleanup manager interface * add rendering flow * refactor loading + init * add renderer support * fix renderer plugin * reformat imports * track errors * fix plugin signature inheritance * name param in interface * update func comment * fix func arg name * introduce class concept * remove func * fix external plugin check * apply changes from pm-experiment * fix core plugins * fix imports * rename interface * comment API interface * add support for testdata plugin * enable alerting + use correct core plugin contracts * slim manager API * fix param name * fix filter * support static routes * fix rendering * tidy rendering * get tests compiling * fix install+uninstall * start finder test * add finder test coverage * start loader tests * add test for core plugins * load core + bundled test * add test for nested plugin loading * add test files * clean interface + fix registering some core plugins * refactoring * reformat and create sub packages * simplify core plugin init * fix ctx cancel scenario * migrate initializer * remove Init() funcs * add test starter * new logger * flesh out initializer tests * refactoring * remove unused svc * refactor rendering flow * fixup loader tests * add enabled helper func * fix logger name * fix data fetchers * fix case where plugin dir doesn't exist * improve coverage + move dupe checking to loader * remove noisy debug logs * register core plugins automagically * add support for renderer in catalog * make private func + fix req validation * use interface * re-add check for renderer in catalog * tidy up from moving to auto reg core plugins * core plugin registrar * guards * copy over core plugins for test infra * all tests green * renames * propagate new interfaces * kill old manager * get compiling * tidy up * update naming * refactor manager test + cleanup * add more cases to finder test * migrate validator to field * more coverage * refactor dupe checking * add test for plugin class * add coverage for initializer * split out rendering * move * fixup tests * fix uss test * fix frontend settings * fix grafanads test * add check when checking sig errors * fix enabled map * fixup * allow manual setup of CM * rename to cloud-monitoring * remove TODO * add installer interface for testing * loader interface returns * tests passing * refactor + add more coverage * support 'stackdriver' * fix frontend settings loading * improve naming based on package name * small tidy * refactor test * fix renderer start * make cloud-monitoring plugin ID clearer * add plugin update test * add integration tests * don't break all if sig can't be calculated * add root URL check test * add more signature verification tests * update DTO name * update enabled plugins comment * update comments * fix linter * revert fe naming change * fix errors endpoint * reset error code field name * re-order test to help verify * assert -> require * pm check * add missing entry + re-order * re-check * dump icon log * verify manager contents first * reformat * apply PR feedback * apply style changes * fix one vs all loading err * improve log output * only start when no signature error * move log * rework plugin update check * fix test * fix multi loading from cfg.PluginSettings * improve log output #2 * add error abstraction to capture errors without registering a plugin * add debug log * add unsigned warning * e2e test attempt * fix logger * set home path * prevent panic * alternate * ugh.. fix home path * return renderer even if not started * make renderer plugin managed * add fallback renderer icon, update renderer badge + prevent changes when renderer is installed * fix icon loading * rollback renderer changes * use correct field * remove unneccessary block * remove newline * remove unused func * fix bundled plugins base + module fields * remove unused field since refactor * add authorizer abstraction * loader only returns plugins expected to run * fix multi log output
2021-11-01 04:53:33 -05:00
PluginID: plugin.ID,
DataSourceInstanceSettings: dsInstanceSettings,
},
Headers: map[string]string{},
}
var dsURL string
if req.PluginContext.DataSourceInstanceSettings != nil {
dsURL = req.PluginContext.DataSourceInstanceSettings.URL
}
err = hs.PluginRequestValidator.Validate(dsURL, c.Req)
if err != nil {
return response.Error(http.StatusForbidden, "Access denied", err)
}
if hs.DataProxy.OAuthTokenService.IsOAuthPassThruEnabled(ds) {
if token := hs.DataProxy.OAuthTokenService.GetCurrentOAuthToken(c.Req.Context(), c.SignedInUser); token != nil {
req.Headers["Authorization"] = fmt.Sprintf("%s %s", token.Type(), token.AccessToken)
idToken, ok := token.Extra("id_token").(string)
if ok && idToken != "" {
req.Headers["X-ID-Token"] = idToken
}
}
}
proxyutil.ClearCookieHeader(c.Req, ds.AllowedCookies(), []string{hs.Cfg.LoginCookieName})
if cookieStr := c.Req.Header.Get("Cookie"); cookieStr != "" {
req.Headers["Cookie"] = cookieStr
}
Plugins: Refactor Plugin Management (#40477) * add core plugin flow * add instrumentation * move func * remove cruft * support external backend plugins * refactor + clean up * remove comments * refactor loader * simplify core plugin path arg * cleanup loggers * move signature validator to plugins package * fix sig packaging * cleanup plugin model * remove unnecessary plugin field * add start+stop for pm * fix failures * add decommissioned state * export fields just to get things flowing * fix comments * set static routes * make image loading idempotent * merge with backend plugin manager * re-use funcs * reorder imports + remove unnecessary interface * add some TODOs + remove unused func * remove unused instrumentation func * simplify client usage * remove import alias * re-use backendplugin.Plugin interface * re order funcs * improve var name * fix log statements * refactor data model * add logic for dupe check during loading * cleanup state setting * refactor loader * cleanup manager interface * add rendering flow * refactor loading + init * add renderer support * fix renderer plugin * reformat imports * track errors * fix plugin signature inheritance * name param in interface * update func comment * fix func arg name * introduce class concept * remove func * fix external plugin check * apply changes from pm-experiment * fix core plugins * fix imports * rename interface * comment API interface * add support for testdata plugin * enable alerting + use correct core plugin contracts * slim manager API * fix param name * fix filter * support static routes * fix rendering * tidy rendering * get tests compiling * fix install+uninstall * start finder test * add finder test coverage * start loader tests * add test for core plugins * load core + bundled test * add test for nested plugin loading * add test files * clean interface + fix registering some core plugins * refactoring * reformat and create sub packages * simplify core plugin init * fix ctx cancel scenario * migrate initializer * remove Init() funcs * add test starter * new logger * flesh out initializer tests * refactoring * remove unused svc * refactor rendering flow * fixup loader tests * add enabled helper func * fix logger name * fix data fetchers * fix case where plugin dir doesn't exist * improve coverage + move dupe checking to loader * remove noisy debug logs * register core plugins automagically * add support for renderer in catalog * make private func + fix req validation * use interface * re-add check for renderer in catalog * tidy up from moving to auto reg core plugins * core plugin registrar * guards * copy over core plugins for test infra * all tests green * renames * propagate new interfaces * kill old manager * get compiling * tidy up * update naming * refactor manager test + cleanup * add more cases to finder test * migrate validator to field * more coverage * refactor dupe checking * add test for plugin class * add coverage for initializer * split out rendering * move * fixup tests * fix uss test * fix frontend settings * fix grafanads test * add check when checking sig errors * fix enabled map * fixup * allow manual setup of CM * rename to cloud-monitoring * remove TODO * add installer interface for testing * loader interface returns * tests passing * refactor + add more coverage * support 'stackdriver' * fix frontend settings loading * improve naming based on package name * small tidy * refactor test * fix renderer start * make cloud-monitoring plugin ID clearer * add plugin update test * add integration tests * don't break all if sig can't be calculated * add root URL check test * add more signature verification tests * update DTO name * update enabled plugins comment * update comments * fix linter * revert fe naming change * fix errors endpoint * reset error code field name * re-order test to help verify * assert -> require * pm check * add missing entry + re-order * re-check * dump icon log * verify manager contents first * reformat * apply PR feedback * apply style changes * fix one vs all loading err * improve log output * only start when no signature error * move log * rework plugin update check * fix test * fix multi loading from cfg.PluginSettings * improve log output #2 * add error abstraction to capture errors without registering a plugin * add debug log * add unsigned warning * e2e test attempt * fix logger * set home path * prevent panic * alternate * ugh.. fix home path * return renderer even if not started * make renderer plugin managed * add fallback renderer icon, update renderer badge + prevent changes when renderer is installed * fix icon loading * rollback renderer changes * use correct field * remove unneccessary block * remove newline * remove unused func * fix bundled plugins base + module fields * remove unused field since refactor * add authorizer abstraction * loader only returns plugins expected to run * fix multi log output
2021-11-01 04:53:33 -05:00
resp, err := hs.pluginClient.CheckHealth(c.Req.Context(), req)
if err != nil {
return translatePluginRequestErrorToAPIError(err)
}
payload := map[string]interface{}{
"status": resp.Status.String(),
"message": resp.Message,
}
// Unmarshal JSONDetails if it's not empty.
if len(resp.JSONDetails) > 0 {
var jsonDetails map[string]interface{}
err = json.Unmarshal(resp.JSONDetails, &jsonDetails)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to unmarshal detailed response from backend plugin", err)
}
payload["details"] = jsonDetails
}
if resp.Status != backend.HealthStatusOk {
return response.JSON(http.StatusBadRequest, payload)
}
return response.JSON(http.StatusOK, payload)
}
Encryption: Refactor securejsondata.SecureJsonData to stop relying on global functions (#38865) * Encryption: Add support to encrypt/decrypt sjd * Add datasources.Service as a proxy to datasources db operations * Encrypt ds.SecureJsonData before calling SQLStore * Move ds cache code into ds service * Fix tlsmanager tests * Fix pluginproxy tests * Remove some securejsondata.GetEncryptedJsonData usages * Add pluginsettings.Service as a proxy for plugin settings db operations * Add AlertNotificationService as a proxy for alert notification db operations * Remove some securejsondata.GetEncryptedJsonData usages * Remove more securejsondata.GetEncryptedJsonData usages * Fix lint errors * Minor fixes * Remove encryption global functions usages from ngalert * Fix lint errors * Minor fixes * Minor fixes * Remove securejsondata.DecryptedValue usage * Refactor the refactor * Remove securejsondata.DecryptedValue usage * Move securejsondata to migrations package * Move securejsondata to migrations package * Minor fix * Fix integration test * Fix integration tests * Undo undesired changes * Fix tests * Add context.Context into encryption methods * Fix tests * Fix tests * Fix tests * Trigger CI * Fix test * Add names to params of encryption service interface * Remove bus from CacheServiceImpl * Add logging * Add keys to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Add missing key to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Undo changes in markdown files * Fix formatting * Add context to secrets service * Rename decryptSecureJsonData to decryptSecureJsonDataFn * Name args in GetDecryptedValueFn * Add template back to NewAlertmanagerNotifier * Copy GetDecryptedValueFn to ngalert * Add logging to pluginsettings * Fix pluginsettings test Co-authored-by: Tania B <yalyna.ts@gmail.com> Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
2021-10-07 09:33:50 -05:00
func (hs *HTTPServer) decryptSecureJsonDataFn(ctx context.Context) func(ds *datasources.DataSource) (map[string]string, error) {
return func(ds *datasources.DataSource) (map[string]string, error) {
return hs.DataSourcesService.DecryptedValues(ctx, ds)
Encryption: Refactor securejsondata.SecureJsonData to stop relying on global functions (#38865) * Encryption: Add support to encrypt/decrypt sjd * Add datasources.Service as a proxy to datasources db operations * Encrypt ds.SecureJsonData before calling SQLStore * Move ds cache code into ds service * Fix tlsmanager tests * Fix pluginproxy tests * Remove some securejsondata.GetEncryptedJsonData usages * Add pluginsettings.Service as a proxy for plugin settings db operations * Add AlertNotificationService as a proxy for alert notification db operations * Remove some securejsondata.GetEncryptedJsonData usages * Remove more securejsondata.GetEncryptedJsonData usages * Fix lint errors * Minor fixes * Remove encryption global functions usages from ngalert * Fix lint errors * Minor fixes * Minor fixes * Remove securejsondata.DecryptedValue usage * Refactor the refactor * Remove securejsondata.DecryptedValue usage * Move securejsondata to migrations package * Move securejsondata to migrations package * Minor fix * Fix integration test * Fix integration tests * Undo undesired changes * Fix tests * Add context.Context into encryption methods * Fix tests * Fix tests * Fix tests * Trigger CI * Fix test * Add names to params of encryption service interface * Remove bus from CacheServiceImpl * Add logging * Add keys to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Add missing key to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Undo changes in markdown files * Fix formatting * Add context to secrets service * Rename decryptSecureJsonData to decryptSecureJsonDataFn * Name args in GetDecryptedValueFn * Add template back to NewAlertmanagerNotifier * Copy GetDecryptedValueFn to ngalert * Add logging to pluginsettings * Fix pluginsettings test Co-authored-by: Tania B <yalyna.ts@gmail.com> Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
2021-10-07 09:33:50 -05:00
}
}
func (hs *HTTPServer) filterDatasourcesByQueryPermission(ctx context.Context, user *user.SignedInUser, ds []*datasources.DataSource) ([]*datasources.DataSource, error) {
query := datasources.DatasourcesPermissionFilterQuery{
User: user,
Datasources: ds,
}
query.Result = ds
if err := hs.DatasourcePermissionsService.FilterDatasourcesBasedOnQueryPermissions(ctx, &query); err != nil {
if !errors.Is(err, permissions.ErrNotImplemented) {
return nil, err
}
return ds, nil
}
return query.Result, nil
}
// swagger:parameters checkDatasourceHealthByID
type CheckDatasourceHealthByIDParams struct {
// in:path
// required:true
DatasourceID string `json:"id"`
}
// swagger:parameters callDatasourceResourceByID
type CallDatasourceResourceByIDParams struct {
// in:path
// required:true
DatasourceID string `json:"id"`
}
// swagger:parameters deleteDataSourceByID
type DeleteDataSourceByIDParams struct {
// in:path
// required:true
DatasourceID string `json:"id"`
}
// swagger:parameters getDataSourceByID
type GetDataSourceByIDParams struct {
// in:path
// required:true
DatasourceID string `json:"id"`
}
// swagger:parameters checkDatasourceHealthWithUID
type CheckDatasourceHealthWithUIDParams struct {
// in:path
// required:true
DatasourceUID string `json:"uid"`
}
// swagger:parameters callDatasourceResourceWithUID
type CallDatasourceResourceWithUIDParams struct {
// in:path
// required:true
DatasourceUID string `json:"uid"`
}
// swagger:parameters deleteDataSourceByUID
type DeleteDataSourceByUIDParams struct {
// in:path
// required:true
DatasourceUID string `json:"uid"`
}
// swagger:parameters getDataSourceByUID
type GetDataSourceByUIDParams struct {
// in:path
// required:true
DatasourceUID string `json:"uid"`
}
// swagger:parameters getDataSourceByName
type GetDataSourceByNameParams struct {
// in:path
// required:true
DatasourceName string `json:"name"`
}
// swagger:parameters deleteDataSourceByName
type DeleteDataSourceByNameParams struct {
// in:path
// required:true
DatasourceName string `json:"name"`
}
// swagger:parameters getDataSourceIdByName
type GetDataSourceIdByNameParams struct {
// in:path
// required:true
DatasourceName string `json:"name"`
}
// swagger:parameters addDataSource
type AddDataSourceParams struct {
// in:body
// required:true
Body datasources.AddDataSourceCommand
}
// swagger:parameters updateDataSourceByID
type UpdateDataSourceByIDParams struct {
// in:body
// required:true
Body datasources.UpdateDataSourceCommand
// in:path
// required:true
DatasourceID string `json:"id"`
}
// swagger:parameters updateDataSourceByUID
type UpdateDataSourceByUIDParams struct {
// in:body
// required:true
Body datasources.UpdateDataSourceCommand
// in:path
// required:true
DatasourceUID string `json:"uid"`
}
// swagger:response getDataSourcesResponse
type GetDataSourcesResponse struct {
// The response message
// in: body
Body dtos.DataSourceList `json:"body"`
}
// swagger:response getDataSourceResponse
type GetDataSourceResponse struct {
// The response message
// in: body
Body dtos.DataSource `json:"body"`
}
// swagger:response createOrUpdateDatasourceResponse
type CreateOrUpdateDatasourceResponse struct {
// The response message
// in: body
Body struct {
// ID Identifier of the new data source.
// required: true
// example: 65
ID int64 `json:"id"`
// Name of the new data source.
// required: true
// example: My Data source
Name string `json:"name"`
// Message Message of the deleted dashboard.
// required: true
// example: Data source added
Message string `json:"message"`
// Datasource properties
// required: true
Datasource dtos.DataSource `json:"datasource"`
} `json:"body"`
}
// swagger:response getDataSourceIDResponse
type GetDataSourceIDresponse struct {
// The response message
// in: body
Body struct {
// ID Identifier of the data source.
// required: true
// example: 65
ID int64 `json:"id"`
} `json:"body"`
}
// swagger:response deleteDataSourceByNameResponse
type DeleteDataSourceByNameResponse struct {
// The response message
// in: body
Body struct {
// ID Identifier of the deleted data source.
// required: true
// example: 65
ID int64 `json:"id"`
// Message Message of the deleted dashboard.
// required: true
// example: Dashboard My Dashboard deleted
Message string `json:"message"`
} `json:"body"`
}