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>
This commit is contained in:
Joan López de la Franca Beltran
2021-10-07 17:33:50 +03:00
committed by GitHub
co-authored by Emil Tullstedt Tania B
parent da813877fb
commit 722c414fef
141 changed files with 1968 additions and 1197 deletions
+4 -2
View File
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/setting"
"github.com/opentracing/opentracing-go"
@@ -47,7 +48,8 @@ func (e *AlertEngine) IsDisabled() bool {
// ProvideAlertEngine returns a new AlertEngine.
func ProvideAlertEngine(renderer rendering.Service, bus bus.Bus, requestValidator models.PluginRequestValidator,
dataService plugins.DataRequestHandler, usageStatsService usagestats.Service, cfg *setting.Cfg) *AlertEngine {
dataService plugins.DataRequestHandler, usageStatsService usagestats.Service, encryptionService encryption.Service,
cfg *setting.Cfg) *AlertEngine {
e := &AlertEngine{
Cfg: cfg,
RenderService: renderer,
@@ -62,7 +64,7 @@ func ProvideAlertEngine(renderer rendering.Service, bus bus.Bus, requestValidato
e.evalHandler = NewEvalHandler(e.DataService)
e.ruleReader = newRuleReader()
e.log = log.New("alerting.engine")
e.resultHandler = newResultHandler(e.RenderService)
e.resultHandler = newResultHandler(e.RenderService, encryptionService.GetDecryptedValue)
e.registerUsageMetrics()
@@ -13,7 +13,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
@@ -21,7 +21,7 @@ import (
func TestEngineTimeouts(t *testing.T) {
Convey("Alerting engine timeout tests", t, func() {
usMock := &usagestats.UsageStatsMock{T: t}
engine := ProvideAlertEngine(nil, nil, nil, nil, usMock, setting.NewCfg())
engine := ProvideAlertEngine(nil, nil, nil, nil, usMock, ossencryption.ProvideService(), setting.NewCfg())
setting.AlertingNotificationTimeout = 30 * time.Second
setting.AlertingMaxAttempts = 3
engine.resultHandler = &FakeResultHandler{}
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
@@ -44,7 +45,7 @@ func TestEngineProcessJob(t *testing.T) {
Convey("Alerting engine job processing", t, func() {
bus := bus.New()
usMock := &usagestats.UsageStatsMock{T: t}
engine := ProvideAlertEngine(nil, bus, nil, nil, usMock, setting.NewCfg())
engine := ProvideAlertEngine(nil, bus, nil, nil, usMock, ossencryption.ProvideService(), setting.NewCfg())
setting.AlertingEvaluationTimeout = 30 * time.Second
setting.AlertingNotificationTimeout = 30 * time.Second
setting.AlertingMaxAttempts = 3
+5 -3
View File
@@ -181,12 +181,14 @@ func TestAlertRuleExtraction(t *testing.T) {
})
t.Run("Alert notifications are in DB", func(t *testing.T) {
sqlstore.InitTestDB(t)
sqlStore := sqlstore.InitTestDB(t)
firstNotification := models.CreateAlertNotificationCommand{Uid: "notifier1", OrgId: 1, Name: "1"}
err = sqlstore.CreateAlertNotificationCommand(&firstNotification)
err = sqlStore.CreateAlertNotificationCommand(&firstNotification)
require.Nil(t, err)
secondNotification := models.CreateAlertNotificationCommand{Uid: "notifier2", OrgId: 1, Name: "2"}
err = sqlstore.CreateAlertNotificationCommand(&secondNotification)
err = sqlStore.CreateAlertNotificationCommand(&secondNotification)
require.Nil(t, err)
json, err := ioutil.ReadFile("./testdata/influxdb-alert.json")
+11 -5
View File
@@ -83,16 +83,18 @@ type ShowWhen struct {
Is string `json:"is"`
}
func newNotificationService(renderService rendering.Service) *notificationService {
func newNotificationService(renderService rendering.Service, decryptFn GetDecryptedValueFn) *notificationService {
return &notificationService{
log: log.New("alerting.notifier"),
renderService: renderService,
decryptFn: decryptFn,
}
}
type notificationService struct {
log log.Logger
renderService rendering.Service
decryptFn GetDecryptedValueFn
}
func (n *notificationService) SendIfNeeded(evalCtx *EvalContext) error {
@@ -250,7 +252,7 @@ func (n *notificationService) getNeededNotifiers(orgID int64, notificationUids [
var result notifierStateSlice
for _, notification := range query.Result {
not, err := InitNotifier(notification)
not, err := InitNotifier(notification, n.decryptFn)
if err != nil {
n.log.Error("Could not create notifier", "notifier", notification.Uid, "error", err)
continue
@@ -280,17 +282,21 @@ func (n *notificationService) getNeededNotifiers(orgID int64, notificationUids [
}
// InitNotifier instantiate a new notifier based on the model.
func InitNotifier(model *models.AlertNotification) (Notifier, error) {
func InitNotifier(model *models.AlertNotification, fn GetDecryptedValueFn) (Notifier, error) {
notifierPlugin, found := notifierFactories[model.Type]
if !found {
return nil, fmt.Errorf("unsupported notification type %q", model.Type)
}
return notifierPlugin.Factory(model)
return notifierPlugin.Factory(model, fn)
}
// GetDecryptedValueFn is a function that returns the decrypted value of
// the given key. If the key is not present, then it returns the fallback value.
type GetDecryptedValueFn func(ctx context.Context, sjd map[string][]byte, key string, fallback string, secret string) string
// NotifierFactory is a signature for creating notifiers.
type NotifierFactory func(notification *models.AlertNotification) (Notifier, error)
type NotifierFactory func(*models.AlertNotification, GetDecryptedValueFn) (Notifier, error)
var notifierFactories = make(map[string]*NotifierPlugin)
+2 -2
View File
@@ -263,7 +263,7 @@ func notificationServiceScenario(t *testing.T, name string, evalCtx *EvalContext
},
}
scenarioCtx.notificationService = newNotificationService(renderService)
scenarioCtx.notificationService = newNotificationService(renderService, nil)
fn(scenarioCtx)
})
}
@@ -279,7 +279,7 @@ type testNotifier struct {
Frequency time.Duration
}
func newTestNotifier(model *models.AlertNotification) (Notifier, error) {
func newTestNotifier(model *models.AlertNotification, _ GetDecryptedValueFn) (Notifier, error) {
uploadImage := true
value, exist := model.Settings.CheckGet("uploadImage")
if exist {
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
)
func init() {
@@ -49,7 +50,7 @@ func init() {
}
// NewAlertmanagerNotifier returns a new Alertmanager notifier
func NewAlertmanagerNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewAlertmanagerNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
urlString := model.Settings.Get("url").MustString()
if urlString == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
@@ -63,7 +64,7 @@ func NewAlertmanagerNotifier(model *models.AlertNotification) (alerting.Notifier
}
}
basicAuthUser := model.Settings.Get("basicAuthUser").MustString()
basicAuthPassword := model.DecryptedValue("basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString())
basicAuthPassword := fn(context.Background(), model.SecureSettings, "basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString(), setting.SecretKey)
return &AlertmanagerNotifier{
NotifierBase: NewNotifierBase(model),
@@ -4,15 +4,14 @@ import (
"context"
"testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/stretchr/testify/assert"
"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/services/alerting"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/validations"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
)
func TestReplaceIllegalCharswithUnderscore(t *testing.T) {
@@ -93,7 +92,7 @@ func TestAlertmanagerNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewAlertmanagerNotifier(model)
_, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -107,7 +106,7 @@ func TestAlertmanagerNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewAlertmanagerNotifier(model)
not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
alertmanagerNotifier := not.(*AlertmanagerNotifier)
So(err, ShouldBeNil)
@@ -126,7 +125,7 @@ func TestAlertmanagerNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewAlertmanagerNotifier(model)
not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
alertmanagerNotifier := not.(*AlertmanagerNotifier)
So(err, ShouldBeNil)
+2 -4
View File
@@ -5,14 +5,12 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/stretchr/testify/assert"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/validations"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
)
func TestShouldSendAlertNotification(t *testing.T) {
+1 -1
View File
@@ -47,7 +47,7 @@ func init() {
})
}
func newDingDingNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func newDingDingNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString()
if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
@@ -4,11 +4,11 @@ import (
"context"
"testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/validations"
. "github.com/smartystreets/goconvey/convey"
)
@@ -24,7 +24,7 @@ func TestDingDingNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := newDingDingNotifier(model)
_, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
Convey("settings should trigger incident", func() {
@@ -37,7 +37,7 @@ func TestDingDingNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := newDingDingNotifier(model)
not, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
notifier := not.(*DingDingNotifier)
So(err, ShouldBeNil)
+1 -1
View File
@@ -51,7 +51,7 @@ func init() {
})
}
func newDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func newDiscordNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
avatar := model.Settings.Get("avatar_url").MustString()
content := model.Settings.Get("content").MustString()
url := model.Settings.Get("url").MustString()
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey"
)
@@ -21,7 +22,7 @@ func TestDiscordNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := newDiscordNotifier(model)
_, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -40,7 +41,7 @@ func TestDiscordNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := newDiscordNotifier(model)
not, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
discordNotifier := not.(*DiscordNotifier)
So(err, ShouldBeNil)
+1 -1
View File
@@ -48,7 +48,7 @@ type EmailNotifier struct {
// NewEmailNotifier is the constructor function
// for the EmailNotifier.
func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewEmailNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
addressesString := model.Settings.Get("addresses").MustString()
singleEmail := model.Settings.Get("singleEmail").MustBool(false)
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey"
)
@@ -21,7 +22,7 @@ func TestEmailNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewEmailNotifier(model)
_, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -38,7 +39,7 @@ func TestEmailNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewEmailNotifier(model)
not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
emailNotifier := not.(*EmailNotifier)
So(err, ShouldBeNil)
@@ -62,7 +63,7 @@ func TestEmailNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewEmailNotifier(model)
not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
emailNotifier := not.(*EmailNotifier)
So(err, ShouldBeNil)
@@ -32,7 +32,7 @@ func init() {
})
}
func newGoogleChatNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func newGoogleChatNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString()
if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey"
)
@@ -21,7 +22,7 @@ func TestGoogleChatNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := newGoogleChatNotifier(model)
_, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -38,7 +39,7 @@ func TestGoogleChatNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := newGoogleChatNotifier(model)
not, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
webhookNotifier := not.(*GoogleChatNotifier)
So(err, ShouldBeNil)
+1 -1
View File
@@ -53,7 +53,7 @@ const (
// NewHipChatNotifier is the constructor functions
// for the HipChatNotifier
func NewHipChatNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewHipChatNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString()
if strings.HasSuffix(url, "/") {
url = url[:len(url)-1]
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey"
)
@@ -22,7 +23,7 @@ func TestHipChatNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewHipChatNotifier(model)
_, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -38,7 +39,7 @@ func TestHipChatNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewHipChatNotifier(model)
not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
hipchatNotifier := not.(*HipChatNotifier)
So(err, ShouldBeNil)
@@ -64,7 +65,7 @@ func TestHipChatNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewHipChatNotifier(model)
not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
hipchatNotifier := not.(*HipChatNotifier)
So(err, ShouldBeNil)
+1 -1
View File
@@ -41,7 +41,7 @@ func init() {
}
// NewKafkaNotifier is the constructor function for the Kafka notifier.
func NewKafkaNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewKafkaNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
endpoint := model.Settings.Get("kafkaRestProxy").MustString()
if endpoint == "" {
return nil, alerting.ValidationError{Reason: "Could not find kafka rest proxy endpoint property in settings"}
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey"
)
@@ -21,7 +22,7 @@ func TestKafkaNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewKafkaNotifier(model)
_, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -39,7 +40,7 @@ func TestKafkaNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewKafkaNotifier(model)
not, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
kafkaNotifier := not.(*KafkaNotifier)
So(err, ShouldBeNil)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers
import (
"context"
"fmt"
"net/url"
@@ -8,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
)
func init() {
@@ -35,8 +37,8 @@ const (
)
// NewLINENotifier is the constructor for the LINE notifier
func NewLINENotifier(model *models.AlertNotification) (alerting.Notifier, error) {
token := model.DecryptedValue("token", model.Settings.Get("token").MustString())
func NewLINENotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
token := fn(context.Background(), model.SecureSettings, "token", model.Settings.Get("token").MustString(), setting.SecretKey)
if token == "" {
return nil, alerting.ValidationError{Reason: "Could not find token in settings"}
}
+3 -2
View File
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey"
)
@@ -20,7 +21,7 @@ func TestLineNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewLINENotifier(model)
_, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
Convey("settings should trigger incident", func() {
@@ -35,7 +36,7 @@ func TestLineNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewLINENotifier(model)
not, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue)
lineNotifier := not.(*LineNotifier)
So(err, ShouldBeNil)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers
import (
"context"
"fmt"
"strconv"
@@ -9,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
)
const (
@@ -82,10 +84,10 @@ const (
)
// NewOpsGenieNotifier is the constructor for OpsGenie.
func NewOpsGenieNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewOpsGenieNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
autoClose := model.Settings.Get("autoClose").MustBool(true)
overridePriority := model.Settings.Get("overridePriority").MustBool(true)
apiKey := model.DecryptedValue("apiKey", model.Settings.Get("apiKey").MustString())
apiKey := fn(context.Background(), model.SecureSettings, "apiKey", model.Settings.Get("apiKey").MustString(), setting.SecretKey)
apiURL := model.Settings.Get("apiUrl").MustString()
if apiKey == "" {
return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"}
@@ -4,12 +4,12 @@ import (
"context"
"testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/validations"
. "github.com/smartystreets/goconvey/convey"
)
@@ -26,7 +26,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewOpsGenieNotifier(model)
_, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -43,7 +43,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewOpsGenieNotifier(model)
not, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
opsgenieNotifier := not.(*OpsGenieNotifier)
So(err, ShouldBeNil)
@@ -67,7 +67,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewOpsGenieNotifier(model)
_, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
So(err, ShouldHaveSameTypeAs, alerting.ValidationError{})
So(err.Error(), ShouldEndWith, "Invalid value for sendTagsAs: \"not_a_valid_value\"")
@@ -90,7 +90,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON,
}
notifier, notifierErr := NewOpsGenieNotifier(model) // unhandled error
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
opsgenieNotifier := notifier.(*OpsGenieNotifier)
@@ -140,7 +140,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON,
}
notifier, notifierErr := NewOpsGenieNotifier(model) // unhandled error
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
opsgenieNotifier := notifier.(*OpsGenieNotifier)
@@ -190,7 +190,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON,
}
notifier, notifierErr := NewOpsGenieNotifier(model) // unhandled error
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
opsgenieNotifier := notifier.(*OpsGenieNotifier)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers
import (
"context"
"os"
"strconv"
"strings"
@@ -11,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
)
func init() {
@@ -74,10 +76,10 @@ var (
)
// NewPagerdutyNotifier is the constructor for the PagerDuty notifier
func NewPagerdutyNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewPagerdutyNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
severity := model.Settings.Get("severity").MustString("critical")
autoResolve := model.Settings.Get("autoResolve").MustBool(false)
key := model.DecryptedValue("integrationKey", model.Settings.Get("integrationKey").MustString())
key := fn(context.Background(), model.SecureSettings, "integrationKey", model.Settings.Get("integrationKey").MustString(), setting.SecretKey)
messageInDetails := model.Settings.Get("messageInDetails").MustBool(false)
if key == "" {
return nil, alerting.ValidationError{Reason: "Could not find integration key property in settings"}
@@ -5,13 +5,13 @@ import (
"strings"
"testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/validations"
. "github.com/smartystreets/goconvey/convey"
)
@@ -40,7 +40,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err = NewPagerdutyNotifier(model)
_, err = NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -56,7 +56,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewPagerdutyNotifier(model)
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
pagerdutyNotifier := not.(*PagerdutyNotifier)
So(err, ShouldBeNil)
@@ -79,7 +79,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewPagerdutyNotifier(model)
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
pagerdutyNotifier := not.(*PagerdutyNotifier)
So(err, ShouldBeNil)
@@ -106,7 +106,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewPagerdutyNotifier(model)
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
pagerdutyNotifier := not.(*PagerdutyNotifier)
So(err, ShouldBeNil)
@@ -131,7 +131,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewPagerdutyNotifier(model)
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier)
@@ -188,7 +188,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewPagerdutyNotifier(model)
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier)
@@ -245,7 +245,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewPagerdutyNotifier(model)
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier)
@@ -315,7 +315,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewPagerdutyNotifier(model)
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier)
@@ -395,7 +395,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewPagerdutyNotifier(model)
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier)
@@ -474,7 +474,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewPagerdutyNotifier(model)
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier)
+6 -3
View File
@@ -2,12 +2,15 @@ package notifiers
import (
"bytes"
"context"
"fmt"
"io"
"mime/multipart"
"os"
"strconv"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
@@ -191,9 +194,9 @@ func init() {
}
// NewPushoverNotifier is the constructor for the Pushover Notifier
func NewPushoverNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
userKey := model.DecryptedValue("userKey", model.Settings.Get("userKey").MustString())
APIToken := model.DecryptedValue("apiToken", model.Settings.Get("apiToken").MustString())
func NewPushoverNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
userKey := fn(context.Background(), model.SecureSettings, "userKey", model.Settings.Get("userKey").MustString(), setting.SecretKey)
APIToken := fn(context.Background(), model.SecureSettings, "apiToken", model.Settings.Get("apiToken").MustString(), setting.SecretKey)
device := model.Settings.Get("device").MustString()
alertingPriority, err := strconv.Atoi(model.Settings.Get("priority").MustString("0")) // default Normal
if err != nil {
@@ -5,12 +5,11 @@ import (
"strings"
"testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/validations"
. "github.com/smartystreets/goconvey/convey"
)
@@ -27,7 +26,7 @@ func TestPushoverNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewPushoverNotifier(model)
_, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -49,7 +48,7 @@ func TestPushoverNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewPushoverNotifier(model)
not, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
pushoverNotifier := not.(*PushoverNotifier)
So(err, ShouldBeNil)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers
import (
"context"
"strconv"
"strings"
@@ -9,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
)
func init() {
@@ -59,7 +61,7 @@ func init() {
}
// NewSensuNotifier is the constructor for the Sensu Notifier.
func NewSensuNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewSensuNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString()
if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
@@ -70,7 +72,7 @@ func NewSensuNotifier(model *models.AlertNotification) (alerting.Notifier, error
URL: url,
User: model.Settings.Get("username").MustString(),
Source: model.Settings.Get("source").MustString(),
Password: model.DecryptedValue("password", model.Settings.Get("password").MustString()),
Password: fn(context.Background(), model.SecureSettings, "password", model.Settings.Get("password").MustString(), setting.SecretKey),
Handler: model.Settings.Get("handler").MustString(),
log: log.New("alerting.notifier.sensu"),
}, nil
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey"
)
@@ -21,7 +22,7 @@ func TestSensuNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewSensuNotifier(model)
_, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -40,7 +41,7 @@ func TestSensuNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewSensuNotifier(model)
not, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
sensuNotifier := not.(*SensuNotifier)
So(err, ShouldBeNil)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers
import (
"context"
"fmt"
"strconv"
"strings"
@@ -11,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
)
func init() {
@@ -70,9 +72,9 @@ func init() {
}
// NewSensuGoNotifier is the constructor for the Sensu Go Notifier.
func NewSensuGoNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewSensuGoNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString()
apikey := model.DecryptedValue("apikey", model.Settings.Get("apikey").MustString())
apikey := fn(context.Background(), model.SecureSettings, "apikey", model.Settings.Get("apikey").MustString(), setting.SecretKey)
if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find URL property in settings"}
@@ -3,11 +3,11 @@ package notifiers
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSensuGoNotifier(t *testing.T) {
@@ -21,7 +21,7 @@ func TestSensuGoNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err = NewSensuGoNotifier(model)
_, err = NewSensuGoNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.Error(t, err)
json = `
@@ -42,7 +42,7 @@ func TestSensuGoNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewSensuGoNotifier(model)
not, err := NewSensuGoNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err)
sensuGoNotifier := not.(*SensuGoNotifier)
+3 -3
View File
@@ -124,8 +124,8 @@ var reRecipient *regexp.Regexp = regexp.MustCompile("^((@[a-z0-9][a-zA-Z0-9._-]*
const slackAPIEndpoint = "https://slack.com/api/chat.postMessage"
// NewSlackNotifier is the constructor for the Slack notifier.
func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
urlStr := model.DecryptedValue("url", model.Settings.Get("url").MustString())
func NewSlackNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
urlStr := fn(context.Background(), model.SecureSettings, "url", model.Settings.Get("url").MustString(), setting.SecretKey)
if urlStr == "" {
urlStr = slackAPIEndpoint
}
@@ -150,7 +150,7 @@ func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error
mentionUsersStr := model.Settings.Get("mentionUsers").MustString()
mentionGroupsStr := model.Settings.Get("mentionGroups").MustString()
mentionChannel := model.Settings.Get("mentionChannel").MustString()
token := model.DecryptedValue("token", model.Settings.Get("token").MustString())
token := fn(context.Background(), model.SecureSettings, "token", model.Settings.Get("token").MustString(), setting.SecretKey)
if token == "" && apiURL.String() == slackAPIEndpoint {
return nil, alerting.ValidationError{
Reason: "token must be specified when using the Slack chat API",
+20 -12
View File
@@ -1,11 +1,13 @@
package notifiers
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -22,7 +24,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err = NewSlackNotifier(model)
_, err = NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
assert.EqualError(t, err, "alert validation error: recipient must be specified when using the Slack chat API")
})
@@ -40,7 +42,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewSlackNotifier(model)
not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err)
slackNotifier := not.(*SlackNotifier)
assert.Equal(t, "ops", slackNotifier.Name)
@@ -78,7 +80,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewSlackNotifier(model)
not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err)
slackNotifier := not.(*SlackNotifier)
assert.Equal(t, "ops", slackNotifier.Name)
@@ -110,9 +112,15 @@ func TestSlackNotifier(t *testing.T) {
settingsJSON, err := simplejson.NewJson([]byte(json))
require.NoError(t, err)
securedSettingsJSON := securejsondata.GetEncryptedJsonData(map[string]string{
"token": "xenc-XXXXXXXX-XXXXXXXX-XXXXXXXXXX",
})
encryptionService := ossencryption.ProvideService()
securedSettingsJSON, err := encryptionService.EncryptJsonData(
context.Background(),
map[string]string{
"token": "xenc-XXXXXXXX-XXXXXXXX-XXXXXXXXXX",
}, setting.SecretKey)
require.NoError(t, err)
model := &models.AlertNotification{
Name: "ops",
Type: "slack",
@@ -120,7 +128,7 @@ func TestSlackNotifier(t *testing.T) {
SecureSettings: securedSettingsJSON,
}
not, err := NewSlackNotifier(model)
not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err)
slackNotifier := not.(*SlackNotifier)
assert.Equal(t, "ops", slackNotifier.Name)
@@ -151,7 +159,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err = NewSlackNotifier(model)
_, err = NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"#open tsdb\"")
})
@@ -170,7 +178,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err = NewSlackNotifier(model)
_, err = NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"@user name\"")
})
@@ -189,7 +197,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err = NewSlackNotifier(model)
_, err = NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"@User\"")
})
@@ -208,7 +216,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewSlackNotifier(model)
not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err)
slackNotifier := not.(*SlackNotifier)
assert.Equal(t, "1ABCDE", slackNotifier.recipient)
+1 -1
View File
@@ -30,7 +30,7 @@ func init() {
}
// NewTeamsNotifier is the constructor for Teams notifier.
func NewTeamsNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewTeamsNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString()
if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey"
)
@@ -21,7 +22,7 @@ func TestTeamsNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewTeamsNotifier(model)
_, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -38,7 +39,7 @@ func TestTeamsNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewTeamsNotifier(model)
not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
teamsNotifier := not.(*TeamsNotifier)
So(err, ShouldBeNil)
@@ -60,7 +61,7 @@ func TestTeamsNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewTeamsNotifier(model)
not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
teamsNotifier := not.(*TeamsNotifier)
So(err, ShouldBeNil)
+4 -2
View File
@@ -2,6 +2,7 @@ package notifiers
import (
"bytes"
"context"
"fmt"
"io"
"mime/multipart"
@@ -11,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
)
const (
@@ -61,12 +63,12 @@ type TelegramNotifier struct {
}
// NewTelegramNotifier is the constructor for the Telegram notifier
func NewTelegramNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewTelegramNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
if model.Settings == nil {
return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
}
botToken := model.DecryptedValue("bottoken", model.Settings.Get("bottoken").MustString())
botToken := fn(context.Background(), model.SecureSettings, "bottoken", model.Settings.Get("bottoken").MustString(), setting.SecretKey)
chatID := model.Settings.Get("chatid").MustString()
uploadImage := model.Settings.Get("uploadImage").MustBool()
@@ -4,11 +4,11 @@ import (
"context"
"testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/validations"
. "github.com/smartystreets/goconvey/convey"
)
@@ -25,7 +25,7 @@ func TestTelegramNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewTelegramNotifier(model)
_, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -43,7 +43,7 @@ func TestTelegramNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewTelegramNotifier(model)
not, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
telegramNotifier := not.(*TelegramNotifier)
So(err, ShouldBeNil)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers
import (
"context"
"fmt"
"net/url"
"strings"
@@ -9,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
)
var (
@@ -69,14 +71,14 @@ type ThreemaNotifier struct {
}
// NewThreemaNotifier is the constructor for the Threema notifier
func NewThreemaNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewThreemaNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
if model.Settings == nil {
return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
}
gatewayID := model.Settings.Get("gateway_id").MustString()
recipientID := model.Settings.Get("recipient_id").MustString()
apiSecret := model.DecryptedValue("api_secret", model.Settings.Get("api_secret").MustString())
apiSecret := fn(context.Background(), model.SecureSettings, "api_secret", model.Settings.Get("api_secret").MustString(), setting.SecretKey)
// Validation
if gatewayID == "" {
@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey"
)
@@ -23,7 +24,7 @@ func TestThreemaNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewThreemaNotifier(model)
_, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -42,7 +43,7 @@ func TestThreemaNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewThreemaNotifier(model)
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil)
threemaNotifier := not.(*ThreemaNotifier)
@@ -69,7 +70,7 @@ func TestThreemaNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewThreemaNotifier(model)
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(not, ShouldBeNil)
var valErr alerting.ValidationError
So(errors.As(err, &valErr), ShouldBeTrue)
@@ -91,7 +92,7 @@ func TestThreemaNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewThreemaNotifier(model)
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(not, ShouldBeNil)
var valErr alerting.ValidationError
So(errors.As(err, &valErr), ShouldBeTrue)
@@ -113,7 +114,7 @@ func TestThreemaNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewThreemaNotifier(model)
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(not, ShouldBeNil)
var valErr alerting.ValidationError
So(errors.As(err, &valErr), ShouldBeTrue)
+1 -1
View File
@@ -47,7 +47,7 @@ func init() {
// NewVictoropsNotifier creates an instance of VictoropsNotifier that
// handles posting notifications to Victorops REST API
func NewVictoropsNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewVictoropsNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
autoResolve := model.Settings.Get("autoResolve").MustBool(true)
url := model.Settings.Get("url").MustString()
if url == "" {
@@ -4,12 +4,12 @@ import (
"context"
"testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/google/go-cmp/cmp"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/validations"
. "github.com/smartystreets/goconvey/convey"
)
@@ -35,7 +35,7 @@ func TestVictoropsNotifier(t *testing.T) {
Settings: settingsJSON,
}
_, err := NewVictoropsNotifier(model)
_, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil)
})
@@ -52,7 +52,7 @@ func TestVictoropsNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewVictoropsNotifier(model)
not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
victoropsNotifier := not.(*VictoropsNotifier)
So(err, ShouldBeNil)
@@ -76,7 +76,7 @@ func TestVictoropsNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewVictoropsNotifier(model)
not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil)
victoropsNotifier := not.(*VictoropsNotifier)
@@ -124,7 +124,7 @@ func TestVictoropsNotifier(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewVictoropsNotifier(model)
not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil)
victoropsNotifier := not.(*VictoropsNotifier)
+4 -2
View File
@@ -1,12 +1,14 @@
package notifiers
import (
"context"
"encoding/json"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
)
func init() {
@@ -58,13 +60,13 @@ func init() {
// NewWebHookNotifier is the constructor for
// the WebHook notifier.
func NewWebHookNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
func NewWebHookNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString()
if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
}
password := model.DecryptedValue("password", model.Settings.Get("password").MustString())
password := fn(context.Background(), model.SecureSettings, "password", model.Settings.Get("password").MustString(), setting.SecretKey)
return &WebhookNotifier{
NotifierBase: NewNotifierBase(model),
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -21,7 +22,7 @@ func TestWebhookNotifier_parsingFromSettings(t *testing.T) {
Settings: settingsJSON,
}
_, err = NewWebHookNotifier(model)
_, err = NewWebHookNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.Error(t, err)
})
@@ -36,7 +37,7 @@ func TestWebhookNotifier_parsingFromSettings(t *testing.T) {
Settings: settingsJSON,
}
not, err := NewWebHookNotifier(model)
not, err := NewWebHookNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err)
webhookNotifier := not.(*WebhookNotifier)
+2 -2
View File
@@ -24,10 +24,10 @@ type defaultResultHandler struct {
log log.Logger
}
func newResultHandler(renderService rendering.Service) *defaultResultHandler {
func newResultHandler(renderService rendering.Service, decryptFn GetDecryptedValueFn) *defaultResultHandler {
return &defaultResultHandler{
log: log.New("alerting.resultHandler"),
notifier: newNotificationService(renderService),
notifier: newNotificationService(renderService, decryptFn),
}
}
+3 -3
View File
@@ -83,16 +83,16 @@ func TestAlertRuleForParsing(t *testing.T) {
}
func TestAlertRuleModel(t *testing.T) {
sqlstore.InitTestDB(t)
sqlStore := sqlstore.InitTestDB(t)
RegisterCondition("test", func(model *simplejson.Json, index int) (Condition, error) {
return &FakeCondition{}, nil
})
firstNotification := models.CreateAlertNotificationCommand{Uid: "notifier1", OrgId: 1, Name: "1"}
err := sqlstore.CreateAlertNotificationCommand(&firstNotification)
err := sqlStore.CreateAlertNotificationCommand(&firstNotification)
require.Nil(t, err)
secondNotification := models.CreateAlertNotificationCommand{Uid: "notifier2", OrgId: 1, Name: "2"}
err = sqlstore.CreateAlertNotificationCommand(&secondNotification)
err = sqlStore.CreateAlertNotificationCommand(&secondNotification)
require.Nil(t, err)
t.Run("Testing alert rule with notification id and uid", func(t *testing.T) {
+102
View File
@@ -0,0 +1,102 @@
package alerting
import (
"context"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
)
type AlertNotificationService struct {
Bus bus.Bus
SQLStore *sqlstore.SQLStore
EncryptionService encryption.Service
}
func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, encryptionService encryption.Service,
) *AlertNotificationService {
s := &AlertNotificationService{
Bus: bus,
SQLStore: store,
EncryptionService: encryptionService,
}
s.Bus.AddHandler(s.GetAlertNotifications)
s.Bus.AddHandlerCtx(s.CreateAlertNotificationCommand)
s.Bus.AddHandlerCtx(s.UpdateAlertNotification)
s.Bus.AddHandler(s.DeleteAlertNotification)
s.Bus.AddHandler(s.GetAllAlertNotifications)
s.Bus.AddHandlerCtx(s.GetOrCreateAlertNotificationState)
s.Bus.AddHandlerCtx(s.SetAlertNotificationStateToCompleteCommand)
s.Bus.AddHandlerCtx(s.SetAlertNotificationStateToPendingCommand)
s.Bus.AddHandler(s.GetAlertNotificationsWithUid)
s.Bus.AddHandler(s.UpdateAlertNotificationWithUid)
s.Bus.AddHandler(s.DeleteAlertNotificationWithUid)
s.Bus.AddHandler(s.GetAlertNotificationsWithUidToSend)
s.Bus.AddHandlerCtx(s.HandleNotificationTestCommand)
return s
}
func (s *AlertNotificationService) GetAlertNotifications(query *models.GetAlertNotificationsQuery) error {
return s.SQLStore.GetAlertNotifications(query)
}
func (s *AlertNotificationService) CreateAlertNotificationCommand(ctx context.Context, cmd *models.CreateAlertNotificationCommand) error {
var err error
cmd.EncryptedSecureSettings, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureSettings, setting.SecretKey)
if err != nil {
return err
}
return s.SQLStore.CreateAlertNotificationCommand(cmd)
}
func (s *AlertNotificationService) UpdateAlertNotification(ctx context.Context, cmd *models.UpdateAlertNotificationCommand) error {
var err error
cmd.EncryptedSecureSettings, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureSettings, setting.SecretKey)
if err != nil {
return err
}
return s.SQLStore.UpdateAlertNotification(cmd)
}
func (s *AlertNotificationService) DeleteAlertNotification(cmd *models.DeleteAlertNotificationCommand) error {
return s.SQLStore.DeleteAlertNotification(cmd)
}
func (s *AlertNotificationService) GetAllAlertNotifications(query *models.GetAllAlertNotificationsQuery) error {
return s.SQLStore.GetAllAlertNotifications(query)
}
func (s *AlertNotificationService) GetOrCreateAlertNotificationState(ctx context.Context, cmd *models.GetOrCreateNotificationStateQuery) error {
return s.SQLStore.GetOrCreateAlertNotificationState(ctx, cmd)
}
func (s *AlertNotificationService) SetAlertNotificationStateToCompleteCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToCompleteCommand) error {
return s.SQLStore.SetAlertNotificationStateToCompleteCommand(ctx, cmd)
}
func (s *AlertNotificationService) SetAlertNotificationStateToPendingCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToPendingCommand) error {
return s.SQLStore.SetAlertNotificationStateToPendingCommand(ctx, cmd)
}
func (s *AlertNotificationService) GetAlertNotificationsWithUid(query *models.GetAlertNotificationsWithUidQuery) error {
return s.SQLStore.GetAlertNotificationsWithUid(query)
}
func (s *AlertNotificationService) UpdateAlertNotificationWithUid(cmd *models.UpdateAlertNotificationWithUidCommand) error {
return s.SQLStore.UpdateAlertNotificationWithUid(cmd)
}
func (s *AlertNotificationService) DeleteAlertNotificationWithUid(cmd *models.DeleteAlertNotificationWithUidCommand) error {
return s.SQLStore.DeleteAlertNotificationWithUid(cmd)
}
func (s *AlertNotificationService) GetAlertNotificationsWithUidToSend(query *models.GetAlertNotificationsWithUidToSendQuery) error {
return s.SQLStore.GetAlertNotificationsWithUidToSend(query)
}
+56
View File
@@ -0,0 +1,56 @@
package alerting
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
)
func TestService(t *testing.T) {
sqlStore := sqlstore.InitTestDB(t)
s := ProvideService(bus.New(), sqlStore, ossencryption.ProvideService())
origSecret := setting.SecretKey
setting.SecretKey = "alert_notification_service_test"
t.Cleanup(func() {
setting.SecretKey = origSecret
})
var an *models.AlertNotification
t.Run("create alert notification should encrypt the secure json data", func(t *testing.T) {
ctx := context.Background()
ss := map[string]string{"password": "12345"}
cmd := models.CreateAlertNotificationCommand{SecureSettings: ss}
err := s.CreateAlertNotificationCommand(ctx, &cmd)
require.NoError(t, err)
an = cmd.Result
decrypted, err := s.EncryptionService.DecryptJsonData(ctx, an.SecureSettings, setting.SecretKey)
require.NoError(t, err)
require.Equal(t, ss, decrypted)
})
t.Run("update alert notification should encrypt the secure json data", func(t *testing.T) {
ctx := context.Background()
ss := map[string]string{"password": "678910"}
cmd := models.UpdateAlertNotificationCommand{Id: an.Id, Settings: simplejson.New(), SecureSettings: ss}
err := s.UpdateAlertNotification(ctx, &cmd)
require.NoError(t, err)
decrypted, err := s.EncryptionService.DecryptJsonData(ctx, cmd.Result.SecureSettings, setting.SecretKey)
require.NoError(t, err)
require.Equal(t, ss, decrypted)
})
}
+14 -12
View File
@@ -6,13 +6,12 @@ import (
"math/rand"
"net/http"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/null"
"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/setting"
)
// NotificationTestCommand initiates an test
@@ -31,12 +30,8 @@ var (
logger = log.New("alerting.testnotification")
)
func init() {
bus.AddHandlerCtx("alerting", handleNotificationTestCommand)
}
func handleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCommand) error {
notifier := newNotificationService(nil)
func (s *AlertNotificationService) HandleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCommand) error {
notifier := newNotificationService(nil, nil)
model := &models.AlertNotification{
Name: cmd.Name,
@@ -56,7 +51,11 @@ func handleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCom
}
if query.Result.SecureSettings != nil {
secureSettingsMap = query.Result.SecureSettings.Decrypt()
var err error
secureSettingsMap, err = s.EncryptionService.DecryptJsonData(ctx, query.Result.SecureSettings, setting.SecretKey)
if err != nil {
return err
}
}
}
@@ -64,10 +63,13 @@ func handleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCom
secureSettingsMap[k] = v
}
model.SecureSettings = securejsondata.GetEncryptedJsonData(secureSettingsMap)
notifiers, err := InitNotifier(model)
var err error
model.SecureSettings, err = s.EncryptionService.EncryptJsonData(ctx, secureSettingsMap, setting.SecretKey)
if err != nil {
return err
}
notifiers, err := InitNotifier(model, s.EncryptionService.GetDecryptedValue)
if err != nil {
logger.Error("Failed to create notifier", "error", err.Error())
return err
@@ -1,6 +1,8 @@
package dashboardsnapshots
import (
"context"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
@@ -22,22 +24,22 @@ func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, encryptionService enc
EncryptionService: encryptionService,
}
s.Bus.AddHandler(s.CreateDashboardSnapshot)
s.Bus.AddHandler(s.GetDashboardSnapshot)
s.Bus.AddHandler(s.DeleteDashboardSnapshot)
s.Bus.AddHandler(s.SearchDashboardSnapshots)
s.Bus.AddHandler(s.DeleteExpiredSnapshots)
s.Bus.AddHandlerCtx(s.CreateDashboardSnapshot)
s.Bus.AddHandlerCtx(s.GetDashboardSnapshot)
s.Bus.AddHandlerCtx(s.DeleteDashboardSnapshot)
s.Bus.AddHandlerCtx(s.SearchDashboardSnapshots)
s.Bus.AddHandlerCtx(s.DeleteExpiredSnapshots)
return s
}
func (s *Service) CreateDashboardSnapshot(cmd *models.CreateDashboardSnapshotCommand) error {
func (s *Service) CreateDashboardSnapshot(ctx context.Context, cmd *models.CreateDashboardSnapshotCommand) error {
marshalledData, err := cmd.Dashboard.Encode()
if err != nil {
return err
}
encryptedDashboard, err := s.EncryptionService.Encrypt(marshalledData, setting.SecretKey)
encryptedDashboard, err := s.EncryptionService.Encrypt(ctx, marshalledData, setting.SecretKey)
if err != nil {
return err
}
@@ -47,14 +49,14 @@ func (s *Service) CreateDashboardSnapshot(cmd *models.CreateDashboardSnapshotCom
return s.SQLStore.CreateDashboardSnapshot(cmd)
}
func (s *Service) GetDashboardSnapshot(query *models.GetDashboardSnapshotQuery) error {
func (s *Service) GetDashboardSnapshot(ctx context.Context, query *models.GetDashboardSnapshotQuery) error {
err := s.SQLStore.GetDashboardSnapshot(query)
if err != nil {
return err
}
if query.Result.DashboardEncrypted != nil {
decryptedDashboard, err := s.EncryptionService.Decrypt(query.Result.DashboardEncrypted, setting.SecretKey)
decryptedDashboard, err := s.EncryptionService.Decrypt(ctx, query.Result.DashboardEncrypted, setting.SecretKey)
if err != nil {
return err
}
@@ -70,14 +72,14 @@ func (s *Service) GetDashboardSnapshot(query *models.GetDashboardSnapshotQuery)
return err
}
func (s *Service) DeleteDashboardSnapshot(cmd *models.DeleteDashboardSnapshotCommand) error {
func (s *Service) DeleteDashboardSnapshot(_ context.Context, cmd *models.DeleteDashboardSnapshotCommand) error {
return s.SQLStore.DeleteDashboardSnapshot(cmd)
}
func (s *Service) SearchDashboardSnapshots(query *models.GetDashboardSnapshotsQuery) error {
func (s *Service) SearchDashboardSnapshots(_ context.Context, query *models.GetDashboardSnapshotsQuery) error {
return s.SQLStore.SearchDashboardSnapshots(query)
}
func (s *Service) DeleteExpiredSnapshots(cmd *models.DeleteExpiredSnapshotsCommand) error {
func (s *Service) DeleteExpiredSnapshots(_ context.Context, cmd *models.DeleteExpiredSnapshotsCommand) error {
return s.SQLStore.DeleteExpiredSnapshots(cmd)
}
@@ -1,6 +1,7 @@
package dashboardsnapshots
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
@@ -32,28 +33,32 @@ func TestDashboardSnapshotsService(t *testing.T) {
require.NoError(t, err)
t.Run("create dashboard snapshot should encrypt the dashboard", func(t *testing.T) {
ctx := context.Background()
cmd := models.CreateDashboardSnapshotCommand{
Key: dashboardKey,
DeleteKey: dashboardKey,
Dashboard: dashboard,
}
err = s.CreateDashboardSnapshot(&cmd)
err = s.CreateDashboardSnapshot(ctx, &cmd)
require.NoError(t, err)
decrypted, err := s.EncryptionService.Decrypt(cmd.Result.DashboardEncrypted, setting.SecretKey)
decrypted, err := s.EncryptionService.Decrypt(ctx, cmd.Result.DashboardEncrypted, setting.SecretKey)
require.NoError(t, err)
require.Equal(t, rawDashboard, decrypted)
})
t.Run("get dashboard snapshot should return the dashboard decrypted", func(t *testing.T) {
ctx := context.Background()
query := models.GetDashboardSnapshotQuery{
Key: dashboardKey,
DeleteKey: dashboardKey,
}
err := s.GetDashboardSnapshot(&query)
err := s.GetDashboardSnapshot(ctx, &query)
require.NoError(t, err)
decrypted, err := query.Result.Dashboard.Encode()
@@ -19,7 +19,7 @@ import (
func ProvideService(dataSourceCache datasources.CacheService, plugReqValidator models.PluginRequestValidator,
pm plugins.Manager, cfg *setting.Cfg, httpClientProvider httpclient.Provider,
oauthTokenService *oauthtoken.Service) *DataSourceProxyService {
oauthTokenService *oauthtoken.Service, dsService *datasources.Service) *DataSourceProxyService {
return &DataSourceProxyService{
DataSourceCache: dataSourceCache,
PluginRequestValidator: plugReqValidator,
@@ -27,6 +27,7 @@ func ProvideService(dataSourceCache datasources.CacheService, plugReqValidator m
Cfg: cfg,
HTTPClientProvider: httpClientProvider,
OAuthTokenService: oauthTokenService,
DataSourcesService: dsService,
}
}
@@ -37,6 +38,7 @@ type DataSourceProxyService struct {
Cfg *setting.Cfg
HTTPClientProvider httpclient.Provider
OAuthTokenService *oauthtoken.Service
DataSourcesService *datasources.Service
}
func (p *DataSourceProxyService) ProxyDataSourceRequest(c *models.ReqContext) {
@@ -73,8 +75,10 @@ func (p *DataSourceProxyService) ProxyDatasourceRequestWithID(c *models.ReqConte
return
}
proxyPath := getProxyPath(c)
proxy, err := pluginproxy.NewDataSourceProxy(ds, plugin, c, proxyPath, p.Cfg, p.HTTPClientProvider, p.OAuthTokenService)
proxy, err := pluginproxy.NewDataSourceProxy(
ds, plugin, c, getProxyPath(c), p.Cfg, p.HTTPClientProvider, p.OAuthTokenService, p.DataSourcesService,
)
if err != nil {
if errors.Is(err, datasource.URLValidationError{}) {
c.JsonApiErr(http.StatusBadRequest, fmt.Sprintf("Invalid data source URL: %q", ds.Url), err)
+9 -2
View File
@@ -43,11 +43,15 @@ func (dc *CacheServiceImpl) GetDatasource(
}
plog.Debug("Querying for data source via SQL store", "id", datasourceID, "orgId", user.OrgId)
ds, err := dc.SQLStore.GetDataSource("", datasourceID, "", user.OrgId)
query := &models.GetDataSourceQuery{Id: datasourceID, OrgId: user.OrgId}
err := dc.SQLStore.GetDataSource(query)
if err != nil {
return nil, err
}
ds := query.Result
if ds.Uid != "" {
dc.CacheService.Set(uidKey(ds.OrgId, ds.Uid), ds, time.Second*5)
}
@@ -78,11 +82,14 @@ func (dc *CacheServiceImpl) GetDatasourceByUID(
}
plog.Debug("Querying for data source via SQL store", "uid", datasourceUID, "orgId", user.OrgId)
ds, err := dc.SQLStore.GetDataSource(datasourceUID, 0, "", user.OrgId)
query := &models.GetDataSourceQuery{Uid: datasourceUID, OrgId: user.OrgId}
err := dc.SQLStore.GetDataSource(query)
if err != nil {
return nil, err
}
ds := query.Result
dc.CacheService.Set(uidCacheKey, ds, time.Second*5)
dc.CacheService.Set(idKey(ds.Id), ds, time.Second*5)
return ds, nil
+373
View File
@@ -0,0 +1,373 @@
package datasources
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"strconv"
"sync"
"time"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
)
type Service struct {
Bus bus.Bus
SQLStore *sqlstore.SQLStore
EncryptionService encryption.Service
ptc proxyTransportCache
dsDecryptionCache secureJSONDecryptionCache
}
type proxyTransportCache struct {
cache map[int64]cachedRoundTripper
sync.Mutex
}
type cachedRoundTripper struct {
updated time.Time
roundTripper http.RoundTripper
}
type secureJSONDecryptionCache struct {
cache map[int64]cachedDecryptedJSON
sync.Mutex
}
type cachedDecryptedJSON struct {
updated time.Time
json map[string]string
}
func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, encryptionService encryption.Service) *Service {
s := &Service{
Bus: bus,
SQLStore: store,
EncryptionService: encryptionService,
ptc: proxyTransportCache{
cache: make(map[int64]cachedRoundTripper),
},
dsDecryptionCache: secureJSONDecryptionCache{
cache: make(map[int64]cachedDecryptedJSON),
},
}
s.Bus.AddHandler(s.GetDataSources)
s.Bus.AddHandler(s.GetDataSourcesByType)
s.Bus.AddHandler(s.GetDataSource)
s.Bus.AddHandlerCtx(s.AddDataSource)
s.Bus.AddHandler(s.DeleteDataSource)
s.Bus.AddHandlerCtx(s.UpdateDataSource)
s.Bus.AddHandler(s.GetDefaultDataSource)
return s
}
func (s *Service) GetDataSource(query *models.GetDataSourceQuery) error {
return s.SQLStore.GetDataSource(query)
}
func (s *Service) GetDataSources(query *models.GetDataSourcesQuery) error {
return s.SQLStore.GetDataSources(query)
}
func (s *Service) GetDataSourcesByType(query *models.GetDataSourcesByTypeQuery) error {
return s.SQLStore.GetDataSourcesByType(query)
}
func (s *Service) AddDataSource(ctx context.Context, cmd *models.AddDataSourceCommand) error {
var err error
cmd.EncryptedSecureJsonData, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureJsonData, setting.SecretKey)
if err != nil {
return err
}
return s.SQLStore.AddDataSource(cmd)
}
func (s *Service) DeleteDataSource(cmd *models.DeleteDataSourceCommand) error {
return s.SQLStore.DeleteDataSource(cmd)
}
func (s *Service) UpdateDataSource(ctx context.Context, cmd *models.UpdateDataSourceCommand) error {
var err error
cmd.EncryptedSecureJsonData, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureJsonData, setting.SecretKey)
if err != nil {
return err
}
return s.SQLStore.UpdateDataSource(cmd)
}
func (s *Service) GetDefaultDataSource(query *models.GetDefaultDataSourceQuery) error {
return s.SQLStore.GetDefaultDataSource(query)
}
func (s *Service) GetHTTPClient(ds *models.DataSource, provider httpclient.Provider) (*http.Client, error) {
transport, err := s.GetHTTPTransport(ds, provider)
if err != nil {
return nil, err
}
return &http.Client{
Timeout: s.getTimeout(ds),
Transport: transport,
}, nil
}
func (s *Service) GetHTTPTransport(ds *models.DataSource, provider httpclient.Provider,
customMiddlewares ...sdkhttpclient.Middleware) (http.RoundTripper, error) {
s.ptc.Lock()
defer s.ptc.Unlock()
if t, present := s.ptc.cache[ds.Id]; present && ds.Updated.Equal(t.updated) {
return t.roundTripper, nil
}
opts, err := s.httpClientOptions(ds)
if err != nil {
return nil, err
}
opts.Middlewares = customMiddlewares
rt, err := provider.GetTransport(*opts)
if err != nil {
return nil, err
}
s.ptc.cache[ds.Id] = cachedRoundTripper{
roundTripper: rt,
updated: ds.Updated,
}
return rt, nil
}
func (s *Service) GetTLSConfig(ds *models.DataSource, httpClientProvider httpclient.Provider) (*tls.Config, error) {
opts, err := s.httpClientOptions(ds)
if err != nil {
return nil, err
}
return httpClientProvider.GetTLSConfig(*opts)
}
func (s *Service) DecryptedValues(ds *models.DataSource) map[string]string {
s.dsDecryptionCache.Lock()
defer s.dsDecryptionCache.Unlock()
if item, present := s.dsDecryptionCache.cache[ds.Id]; present && ds.Updated.Equal(item.updated) {
return item.json
}
json, err := s.EncryptionService.DecryptJsonData(context.Background(), ds.SecureJsonData, setting.SecretKey)
if err != nil {
return map[string]string{}
}
s.dsDecryptionCache.cache[ds.Id] = cachedDecryptedJSON{
updated: ds.Updated,
json: json,
}
return json
}
func (s *Service) DecryptedValue(ds *models.DataSource, key string) (string, bool) {
value, exists := s.DecryptedValues(ds)[key]
return value, exists
}
func (s *Service) DecryptedBasicAuthPassword(ds *models.DataSource) string {
if value, ok := s.DecryptedValue(ds, "basicAuthPassword"); ok {
return value
}
return ds.BasicAuthPassword
}
func (s *Service) DecryptedPassword(ds *models.DataSource) string {
if value, ok := s.DecryptedValue(ds, "password"); ok {
return value
}
return ds.Password
}
func (s *Service) httpClientOptions(ds *models.DataSource) (*sdkhttpclient.Options, error) {
tlsOptions := s.dsTLSOptions(ds)
timeouts := &sdkhttpclient.TimeoutOptions{
Timeout: s.getTimeout(ds),
DialTimeout: sdkhttpclient.DefaultTimeoutOptions.DialTimeout,
KeepAlive: sdkhttpclient.DefaultTimeoutOptions.KeepAlive,
TLSHandshakeTimeout: sdkhttpclient.DefaultTimeoutOptions.TLSHandshakeTimeout,
ExpectContinueTimeout: sdkhttpclient.DefaultTimeoutOptions.ExpectContinueTimeout,
MaxConnsPerHost: sdkhttpclient.DefaultTimeoutOptions.MaxConnsPerHost,
MaxIdleConns: sdkhttpclient.DefaultTimeoutOptions.MaxIdleConns,
MaxIdleConnsPerHost: sdkhttpclient.DefaultTimeoutOptions.MaxIdleConnsPerHost,
IdleConnTimeout: sdkhttpclient.DefaultTimeoutOptions.IdleConnTimeout,
}
opts := &sdkhttpclient.Options{
Timeouts: timeouts,
Headers: s.getCustomHeaders(ds.JsonData, s.DecryptedValues(ds)),
Labels: map[string]string{
"datasource_name": ds.Name,
"datasource_uid": ds.Uid,
},
TLS: &tlsOptions,
}
if ds.JsonData != nil {
opts.CustomOptions = ds.JsonData.MustMap()
}
if ds.BasicAuth {
opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{
User: ds.BasicAuthUser,
Password: s.DecryptedBasicAuthPassword(ds),
}
} else if ds.User != "" {
opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{
User: ds.User,
Password: s.DecryptedPassword(ds),
}
}
if ds.JsonData != nil && ds.JsonData.Get("azureAuth").MustBool() {
credentials, err := azcredentials.FromDatasourceData(ds.JsonData.MustMap(), s.DecryptedValues(ds))
if err != nil {
err = fmt.Errorf("invalid Azure credentials: %s", err)
return nil, err
}
opts.CustomOptions["_azureAuth"] = true
if credentials != nil {
opts.CustomOptions["_azureCredentials"] = credentials
}
}
if ds.JsonData != nil && ds.JsonData.Get("sigV4Auth").MustBool(false) && setting.SigV4AuthEnabled {
opts.SigV4 = &sdkhttpclient.SigV4Config{
Service: awsServiceNamespace(ds.Type),
Region: ds.JsonData.Get("sigV4Region").MustString(),
AssumeRoleARN: ds.JsonData.Get("sigV4AssumeRoleArn").MustString(),
AuthType: ds.JsonData.Get("sigV4AuthType").MustString(),
ExternalID: ds.JsonData.Get("sigV4ExternalId").MustString(),
Profile: ds.JsonData.Get("sigV4Profile").MustString(),
}
if val, exists := s.DecryptedValue(ds, "sigV4AccessKey"); exists {
opts.SigV4.AccessKey = val
}
if val, exists := s.DecryptedValue(ds, "sigV4SecretKey"); exists {
opts.SigV4.SecretKey = val
}
}
return opts, nil
}
func (s *Service) dsTLSOptions(ds *models.DataSource) sdkhttpclient.TLSOptions {
var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool
var serverName string
if ds.JsonData != nil {
tlsClientAuth = ds.JsonData.Get("tlsAuth").MustBool(false)
tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false)
tlsSkipVerify = ds.JsonData.Get("tlsSkipVerify").MustBool(false)
serverName = ds.JsonData.Get("serverName").MustString()
}
opts := sdkhttpclient.TLSOptions{
InsecureSkipVerify: tlsSkipVerify,
ServerName: serverName,
}
if tlsClientAuth || tlsAuthWithCACert {
if tlsAuthWithCACert {
if val, exists := s.DecryptedValue(ds, "tlsCACert"); exists && len(val) > 0 {
opts.CACertificate = val
}
}
if tlsClientAuth {
if val, exists := s.DecryptedValue(ds, "tlsClientCert"); exists && len(val) > 0 {
opts.ClientCertificate = val
}
if val, exists := s.DecryptedValue(ds, "tlsClientKey"); exists && len(val) > 0 {
opts.ClientKey = val
}
}
}
return opts
}
func (s *Service) getTimeout(ds *models.DataSource) time.Duration {
timeout := 0
if ds.JsonData != nil {
timeout = ds.JsonData.Get("timeout").MustInt()
if timeout <= 0 {
if timeoutStr := ds.JsonData.Get("timeout").MustString(); timeoutStr != "" {
if t, err := strconv.Atoi(timeoutStr); err == nil {
timeout = t
}
}
}
}
if timeout <= 0 {
return sdkhttpclient.DefaultTimeoutOptions.Timeout
}
return time.Duration(timeout) * time.Second
}
// getCustomHeaders returns a map with all the to be set headers
// The map key represents the HeaderName and the value represents this header's value
func (s *Service) getCustomHeaders(jsonData *simplejson.Json, decryptedValues map[string]string) map[string]string {
headers := make(map[string]string)
if jsonData == nil {
return headers
}
index := 1
for {
headerNameSuffix := fmt.Sprintf("httpHeaderName%d", index)
headerValueSuffix := fmt.Sprintf("httpHeaderValue%d", index)
key := jsonData.Get(headerNameSuffix).MustString()
if key == "" {
// No (more) header values are available
break
}
if val, ok := decryptedValues[headerValueSuffix]; ok {
headers[key] = val
}
index++
}
return headers
}
func awsServiceNamespace(dsType string) string {
switch dsType {
case models.DS_ES, models.DS_ES_OPEN_DISTRO:
return "es"
case models.DS_PROMETHEUS:
return "aps"
default:
panic(fmt.Sprintf("Unsupported datasource %q", dsType))
}
}
+675
View File
@@ -0,0 +1,675 @@
package datasources
import (
"context"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestService(t *testing.T) {
sqlStore := sqlstore.InitTestDB(t)
s := ProvideService(bus.New(), sqlStore, ossencryption.ProvideService())
origSecret := setting.SecretKey
setting.SecretKey = "datasources_service_test"
t.Cleanup(func() {
setting.SecretKey = origSecret
})
var ds *models.DataSource
t.Run("create datasource should encrypt the secure json data", func(t *testing.T) {
ctx := context.Background()
sjd := map[string]string{"password": "12345"}
cmd := models.AddDataSourceCommand{SecureJsonData: sjd}
err := s.AddDataSource(ctx, &cmd)
require.NoError(t, err)
ds = cmd.Result
decrypted, err := s.EncryptionService.DecryptJsonData(ctx, ds.SecureJsonData, setting.SecretKey)
require.NoError(t, err)
require.Equal(t, sjd, decrypted)
})
t.Run("update datasource should encrypt the secure json data", func(t *testing.T) {
ctx := context.Background()
sjd := map[string]string{"password": "678910"}
cmd := models.UpdateDataSourceCommand{Id: ds.Id, OrgId: ds.OrgId, SecureJsonData: sjd}
err := s.UpdateDataSource(ctx, &cmd)
require.NoError(t, err)
decrypted, err := s.EncryptionService.DecryptJsonData(ctx, cmd.Result.SecureJsonData, setting.SecretKey)
require.NoError(t, err)
require.Equal(t, sjd, decrypted)
})
}
//nolint:goconst
func TestService_GetHttpTransport(t *testing.T) {
t.Run("Should use cached proxy", func(t *testing.T) {
var configuredTransport *http.Transport
provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
configuredTransport = transport
},
})
ds := models.DataSource{
Id: 1,
Url: "http://k8s:8001",
Type: "Kubernetes",
}
dsService := ProvideService(bus.New(), nil, ossencryption.ProvideService())
rt1, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err)
require.NotNil(t, rt1)
tr1 := configuredTransport
rt2, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err)
require.NotNil(t, rt2)
tr2 := configuredTransport
require.Same(t, tr1, tr2)
require.False(t, tr1.TLSClientConfig.InsecureSkipVerify)
require.Empty(t, tr1.TLSClientConfig.Certificates)
require.Nil(t, tr1.TLSClientConfig.RootCAs)
})
t.Run("Should not use cached proxy when datasource updated", func(t *testing.T) {
var configuredTransport *http.Transport
provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
configuredTransport = transport
},
})
setting.SecretKey = "password"
json := simplejson.New()
json.Set("tlsAuthWithCACert", true)
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
tlsCaCert, err := encryptionService.Encrypt(context.Background(), []byte(caCert), "password")
require.NoError(t, err)
ds := models.DataSource{
Id: 1,
Url: "http://k8s:8001",
Type: "Kubernetes",
SecureJsonData: map[string][]byte{"tlsCACert": tlsCaCert},
Updated: time.Now().Add(-2 * time.Minute),
}
rt1, err := dsService.GetHTTPTransport(&ds, provider)
require.NotNil(t, rt1)
require.NoError(t, err)
tr1 := configuredTransport
require.False(t, tr1.TLSClientConfig.InsecureSkipVerify)
require.Empty(t, tr1.TLSClientConfig.Certificates)
require.Nil(t, tr1.TLSClientConfig.RootCAs)
ds.JsonData = nil
ds.SecureJsonData = map[string][]byte{}
ds.Updated = time.Now()
rt2, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err)
require.NotNil(t, rt2)
tr2 := configuredTransport
require.NotSame(t, tr1, tr2)
require.Nil(t, tr2.TLSClientConfig.RootCAs)
})
t.Run("Should set TLS client authentication enabled if configured in JsonData", func(t *testing.T) {
var configuredTransport *http.Transport
provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
configuredTransport = transport
},
})
setting.SecretKey = "password"
json := simplejson.New()
json.Set("tlsAuth", true)
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
tlsClientCert, err := encryptionService.Encrypt(context.Background(), []byte(clientCert), "password")
require.NoError(t, err)
tlsClientKey, err := encryptionService.Encrypt(context.Background(), []byte(clientKey), "password")
require.NoError(t, err)
ds := models.DataSource{
Id: 1,
Url: "http://k8s:8001",
Type: "Kubernetes",
JsonData: json,
SecureJsonData: map[string][]byte{
"tlsClientCert": tlsClientCert,
"tlsClientKey": tlsClientKey,
},
}
rt, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err)
require.NotNil(t, rt)
tr := configuredTransport
require.False(t, tr.TLSClientConfig.InsecureSkipVerify)
require.Len(t, tr.TLSClientConfig.Certificates, 1)
})
t.Run("Should set user-supplied TLS CA if configured in JsonData", func(t *testing.T) {
var configuredTransport *http.Transport
provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
configuredTransport = transport
},
})
setting.SecretKey = "password"
json := simplejson.New()
json.Set("tlsAuthWithCACert", true)
json.Set("serverName", "server-name")
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
tlsCaCert, err := encryptionService.Encrypt(context.Background(), []byte(caCert), "password")
require.NoError(t, err)
ds := models.DataSource{
Id: 1,
Url: "http://k8s:8001",
Type: "Kubernetes",
JsonData: json,
SecureJsonData: map[string][]byte{
"tlsCACert": tlsCaCert,
},
}
rt, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err)
require.NotNil(t, rt)
tr := configuredTransport
require.False(t, tr.TLSClientConfig.InsecureSkipVerify)
require.Len(t, tr.TLSClientConfig.RootCAs.Subjects(), 1)
require.Equal(t, "server-name", tr.TLSClientConfig.ServerName)
})
t.Run("Should set skip TLS verification if configured in JsonData", func(t *testing.T) {
var configuredTransport *http.Transport
provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
configuredTransport = transport
},
})
json := simplejson.New()
json.Set("tlsSkipVerify", true)
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
ds := models.DataSource{
Id: 1,
Url: "http://k8s:8001",
Type: "Kubernetes",
JsonData: json,
}
rt1, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err)
require.NotNil(t, rt1)
tr1 := configuredTransport
rt2, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err)
require.NotNil(t, rt2)
tr2 := configuredTransport
require.Same(t, tr1, tr2)
require.True(t, tr1.TLSClientConfig.InsecureSkipVerify)
})
t.Run("Should set custom headers if configured in JsonData", func(t *testing.T) {
provider := httpclient.NewProvider()
json := simplejson.NewFromAny(map[string]interface{}{
"httpHeaderName1": "Authorization",
})
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
encryptedData, err := encryptionService.Encrypt(context.Background(), []byte(`Bearer xf5yhfkpsnmgo`), setting.SecretKey)
require.NoError(t, err)
ds := models.DataSource{
Id: 1,
Url: "http://k8s:8001",
Type: "Kubernetes",
JsonData: json,
SecureJsonData: map[string][]byte{"httpHeaderValue1": encryptedData},
}
headers := dsService.getCustomHeaders(json, map[string]string{"httpHeaderValue1": "Bearer xf5yhfkpsnmgo"})
require.Equal(t, "Bearer xf5yhfkpsnmgo", headers["Authorization"])
// 1. Start HTTP test server which checks the request headers
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") == "Bearer xf5yhfkpsnmgo" {
w.WriteHeader(200)
_, err := w.Write([]byte("Ok"))
require.NoError(t, err)
return
}
w.WriteHeader(403)
_, err := w.Write([]byte("Invalid bearer token provided"))
require.NoError(t, err)
}))
defer backend.Close()
// 2. Get HTTP transport from datasource which uses the test server as backend
ds.Url = backend.URL
rt, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err)
require.NotNil(t, rt)
// 3. Send test request which should have the Authorization header set
req := httptest.NewRequest("GET", backend.URL+"/test-headers", nil)
res, err := rt.RoundTrip(req)
require.NoError(t, err)
t.Cleanup(func() {
err := res.Body.Close()
require.NoError(t, err)
})
body, err := ioutil.ReadAll(res.Body)
require.NoError(t, err)
bodyStr := string(body)
require.Equal(t, "Ok", bodyStr)
})
t.Run("Should use request timeout if configured in JsonData", func(t *testing.T) {
provider := httpclient.NewProvider()
json := simplejson.NewFromAny(map[string]interface{}{
"timeout": 19,
})
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
ds := models.DataSource{
Id: 1,
Url: "http://k8s:8001",
Type: "Kubernetes",
JsonData: json,
}
client, err := dsService.GetHTTPClient(&ds, provider)
require.NoError(t, err)
require.NotNil(t, client)
require.Equal(t, 19*time.Second, client.Timeout)
})
t.Run("Should populate SigV4 options if configured in JsonData", func(t *testing.T) {
var configuredOpts sdkhttpclient.Options
provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
configuredOpts = opts
},
})
origSigV4Enabled := setting.SigV4AuthEnabled
setting.SigV4AuthEnabled = true
t.Cleanup(func() {
setting.SigV4AuthEnabled = origSigV4Enabled
})
json, err := simplejson.NewJson([]byte(`{ "sigV4Auth": true }`))
require.NoError(t, err)
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
ds := models.DataSource{
Type: models.DS_ES,
JsonData: json,
}
_, err = dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err)
require.NotNil(t, configuredOpts)
require.NotNil(t, configuredOpts.SigV4)
require.Equal(t, "es", configuredOpts.SigV4.Service)
})
}
func TestService_getTimeout(t *testing.T) {
originalTimeout := sdkhttpclient.DefaultTimeoutOptions.Timeout
sdkhttpclient.DefaultTimeoutOptions.Timeout = 60 * time.Second
t.Cleanup(func() {
sdkhttpclient.DefaultTimeoutOptions.Timeout = originalTimeout
})
testCases := []struct {
jsonData *simplejson.Json
expectedTimeout time.Duration
}{
{jsonData: simplejson.New(), expectedTimeout: 60 * time.Second},
{jsonData: simplejson.NewFromAny(map[string]interface{}{"timeout": nil}), expectedTimeout: 60 * time.Second},
{jsonData: simplejson.NewFromAny(map[string]interface{}{"timeout": 0}), expectedTimeout: 60 * time.Second},
{jsonData: simplejson.NewFromAny(map[string]interface{}{"timeout": 1}), expectedTimeout: time.Second},
{jsonData: simplejson.NewFromAny(map[string]interface{}{"timeout": "2"}), expectedTimeout: 2 * time.Second},
}
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
for _, tc := range testCases {
ds := &models.DataSource{
JsonData: tc.jsonData,
}
assert.Equal(t, tc.expectedTimeout, dsService.getTimeout(ds))
}
}
func TestService_DecryptedValue(t *testing.T) {
t.Run("When datasource hasn't been updated, encrypted JSON should be fetched from cache", func(t *testing.T) {
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
encryptedJsonData, err := encryptionService.EncryptJsonData(
context.Background(),
map[string]string{
"password": "password",
}, setting.SecretKey)
require.NoError(t, err)
ds := models.DataSource{
Id: 1,
Type: models.DS_INFLUXDB_08,
JsonData: simplejson.New(),
User: "user",
SecureJsonData: encryptedJsonData,
}
// Populate cache
password, ok := dsService.DecryptedValue(&ds, "password")
require.True(t, ok)
require.Equal(t, "password", password)
encryptedJsonData, err = encryptionService.EncryptJsonData(
context.Background(),
map[string]string{
"password": "",
}, setting.SecretKey)
require.NoError(t, err)
ds.SecureJsonData = encryptedJsonData
password, ok = dsService.DecryptedValue(&ds, "password")
require.True(t, ok)
require.Equal(t, "password", password)
})
t.Run("When datasource is updated, encrypted JSON should not be fetched from cache", func(t *testing.T) {
encryptionService := ossencryption.ProvideService()
encryptedJsonData, err := encryptionService.EncryptJsonData(
context.Background(),
map[string]string{
"password": "password",
}, setting.SecretKey)
require.NoError(t, err)
ds := models.DataSource{
Id: 1,
Type: models.DS_INFLUXDB_08,
JsonData: simplejson.New(),
User: "user",
SecureJsonData: encryptedJsonData,
}
dsService := ProvideService(bus.New(), nil, encryptionService)
// Populate cache
password, ok := dsService.DecryptedValue(&ds, "password")
require.True(t, ok)
require.Equal(t, "password", password)
ds.SecureJsonData, err = encryptionService.EncryptJsonData(
context.Background(),
map[string]string{
"password": "",
}, setting.SecretKey)
ds.Updated = time.Now()
require.NoError(t, err)
password, ok = dsService.DecryptedValue(&ds, "password")
require.True(t, ok)
require.Empty(t, password)
})
}
func TestService_HTTPClientOptions(t *testing.T) {
emptyJsonData := simplejson.New()
emptySecureJsonData := map[string][]byte{}
ds := models.DataSource{
Id: 1,
Url: "https://api.example.com",
Type: "prometheus",
}
t.Run("Azure authentication", func(t *testing.T) {
t.Run("should be disabled if not enabled in JsonData", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"])
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
})
t.Run("should be enabled if enabled in JsonData without credentials configured", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureAuth": true,
})
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
assert.Equal(t, true, opts.CustomOptions["_azureAuth"])
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
})
t.Run("should be enabled if enabled in JsonData with credentials configured", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureAuth": true,
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
})
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
assert.Equal(t, true, opts.CustomOptions["_azureAuth"])
require.Contains(t, opts.CustomOptions, "_azureCredentials")
credentials := opts.CustomOptions["_azureCredentials"]
assert.IsType(t, &azcredentials.AzureManagedIdentityCredentials{}, credentials)
})
t.Run("should be disabled if disabled in JsonData even with credentials configured", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureAuth": false,
"azureCredentials": map[string]interface{}{
"authType": "msi",
},
})
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"])
assert.NotContains(t, opts.CustomOptions, "_azureCredentials")
})
t.Run("should fail if credentials are invalid", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureAuth": true,
"azureCredentials": "invalid",
})
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
_, err := dsService.httpClientOptions(&ds)
assert.Error(t, err)
})
t.Run("should pass resourceId from JsonData", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData })
ds.JsonData = simplejson.NewFromAny(map[string]interface{}{
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5",
})
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err)
require.Contains(t, opts.CustomOptions, "azureEndpointResourceId")
azureEndpointResourceId := opts.CustomOptions["azureEndpointResourceId"]
assert.Equal(t, "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5", azureEndpointResourceId)
})
})
}
const caCert string = `-----BEGIN CERTIFICATE-----
MIIDATCCAemgAwIBAgIJAMQ5hC3CPDTeMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV
BAMMDGNhLWs4cy1zdGhsbTAeFw0xNjEwMjcwODQyMjdaFw00NDAzMTQwODQyMjda
MBcxFTATBgNVBAMMDGNhLWs4cy1zdGhsbTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAMLe2AmJ6IleeUt69vgNchOjjmxIIxz5sp1vFu94m1vUip7CqnOg
QkpUsHeBPrGYv8UGloARCL1xEWS+9FVZeXWQoDmbC0SxXhFwRIESNCET7Q8KMi/4
4YPvnMLGZi3Fjwxa8BdUBCN1cx4WEooMVTWXm7RFMtZgDfuOAn3TNXla732sfT/d
1HNFrh48b0wA+HhmA3nXoBnBEblA665hCeo7lIAdRr0zJxJpnFnWXkyTClsAUTMN
iL905LdBiiIRenojipfKXvMz88XSaWTI7JjZYU3BvhyXndkT6f12cef3I96NY3WJ
0uIK4k04WrbzdYXMU3rN6NqlvbHqnI+E7aMCAwEAAaNQME4wHQYDVR0OBBYEFHHx
2+vSPw9bECHj3O51KNo5VdWOMB8GA1UdIwQYMBaAFHHx2+vSPw9bECHj3O51KNo5
VdWOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH2eV5NcV3LBJHs9
I+adbiTPg2vyumrGWwy73T0X8Dtchgt8wU7Q9b9Ucg2fOTmSSyS0iMqEu1Yb2ORB
CknM9mixHC9PwEBbkGCom3VVkqdLwSP6gdILZgyLoH4i8sTUz+S1yGPepi+Vzhs7
adOXtryjcGnwft6HdfKPNklMOHFnjw6uqpho54oj/z55jUpicY/8glDHdrr1bh3k
MHuiWLGewHXPvxfG6UoUx1te65IhifVcJGFZDQwfEmhBflfCmtAJlZEsgTLlBBCh
FHoXIyGOdq1chmRVocdGBCF8fUoGIbuF14r53rpvcbEKtKnnP8+96luKAZLq0a4n
3lb92xM=
-----END CERTIFICATE-----`
const clientCert string = `
-----BEGIN CERTIFICATE-----
MIICsjCCAZoCCQCcd8sOfstQLzANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxj
YS1rOHMtc3RobG0wHhcNMTYxMTAyMDkyNTE1WhcNMTcxMTAyMDkyNTE1WjAfMR0w
GwYDVQQDDBRhZG0tZGFuaWVsLWs4cy1zdGhsbTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAOMliaWyNEUJKM37vWCl5bGub3lMicyRAqGQyY/qxD9yKKM2
FbucVcmWmg5vvTqQVl5rlQ+c7GI8OD6ptmFl8a26coEki7bFr8bkpSyBSEc5p27b
Z0ORFSqBHWHQbr9PkxPLYW6T3gZYUtRYv3OQgGxLXlvUh85n/mQfuR3N1FgmShHo
GtAFi/ht6leXa0Ms+jNSDLCmXpJm1GIEqgyKX7K3+g3vzo9coYqXq4XTa8Efs2v8
SCwqWfBC3rHfgs/5DLB8WT4Kul8QzxkytzcaBQfRfzhSV6bkgm7oTzt2/1eRRsf4
YnXzLE9YkCC9sAn+Owzqf+TYC1KRluWDfqqBTJUCAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAdMsZg6edWGC+xngizn0uamrUg1ViaDqUsz0vpzY5NWLA4MsBc4EtxWRP
ueQvjUimZ3U3+AX0YWNLIrH1FCVos2jdij/xkTUmHcwzr8rQy+B17cFi+a8jtpgw
AU6WWoaAIEhhbWQfth/Diz3mivl1ARB+YqiWca2mjRPLTPcKJEURDVddQ423el0Q
4JNxS5icu7T2zYTYHAo/cT9zVdLZl0xuLxYm3asK1IONJ/evxyVZima3il6MPvhe
58Hwz+m+HdqHxi24b/1J/VKYbISG4huOQCdLzeNXgvwFlGPUmHSnnKo1/KbQDAR5
llG/Sw5+FquFuChaA6l5KWy7F3bQyA==
-----END CERTIFICATE-----`
const clientKey string = `-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA4yWJpbI0RQkozfu9YKXlsa5veUyJzJECoZDJj+rEP3IoozYV
u5xVyZaaDm+9OpBWXmuVD5zsYjw4Pqm2YWXxrbpygSSLtsWvxuSlLIFIRzmnbttn
Q5EVKoEdYdBuv0+TE8thbpPeBlhS1Fi/c5CAbEteW9SHzmf+ZB+5Hc3UWCZKEega
0AWL+G3qV5drQyz6M1IMsKZekmbUYgSqDIpfsrf6De/Oj1yhiperhdNrwR+za/xI
LCpZ8ELesd+Cz/kMsHxZPgq6XxDPGTK3NxoFB9F/OFJXpuSCbuhPO3b/V5FGx/hi
dfMsT1iQIL2wCf47DOp/5NgLUpGW5YN+qoFMlQIDAQABAoIBAQCzy4u312XeW1Cs
Mx6EuOwmh59/ESFmBkZh4rxZKYgrfE5EWlQ7i5SwG4BX+wR6rbNfy6JSmHDXlTkk
CKvvToVNcW6fYHEivDnVojhIERFIJ4+rhQmpBtcNLOQ3/4cZ8X/GxE6b+3lb5l+x
64mnjPLKRaIr5/+TVuebEy0xNTJmjnJ7yiB2HRz7uXEQaVSk/P7KAkkyl/9J3/LM
8N9AX1w6qDaNQZ4/P0++1H4SQenosM/b/GqGTomarEk/GE0NcB9rzmR9VCXa7FRh
WV5jyt9vUrwIEiK/6nUnOkGO8Ei3kB7Y+e+2m6WdaNoU5RAfqXmXa0Q/a0lLRruf
vTMo2WrBAoGBAPRaK4cx76Q+3SJ/wfznaPsMM06OSR8A3ctKdV+ip/lyKtb1W8Pz
k8MYQDH7GwPtSu5QD8doL00pPjugZL/ba7X9nAsI+pinyEErfnB9y7ORNEjIYYzs
DiqDKup7ANgw1gZvznWvb9Ge0WUSXvWS0pFkgootQAf+RmnnbWGH6l6RAoGBAO35
aGUrLro5u9RD24uSXNU3NmojINIQFK5dHAT3yl0BBYstL43AEsye9lX95uMPTvOQ
Cqcn42Hjp/bSe3n0ObyOZeXVrWcDFAfE0wwB1BkvL1lpgnFO9+VQORlH4w3Ppnpo
jcPkR2TFeDaAYtvckhxe/Bk3OnuFmnsQ3VzM75fFAoGBAI6PvS2XeNU+yA3EtA01
hg5SQ+zlHswz2TMuMeSmJZJnhY78f5mHlwIQOAPxGQXlf/4iP9J7en1uPpzTK3S0
M9duK4hUqMA/w5oiIhbHjf0qDnMYVbG+V1V+SZ+cPBXmCDihKreGr5qBKnHpkfV8
v9WL6o1rcRw4wiQvnaV1gsvBAoGBALtzVTczr6gDKCAIn5wuWy+cQSGTsBunjRLX
xuVm5iEiV+KMYkPvAx/pKzMLP96lRVR3ptyKgAKwl7LFk3u50+zh4gQLr35QH2wL
Lw7rNc3srAhrItPsFzqrWX6/cGuFoKYVS239l/sZzRppQPXcpb7xVvTp2whHcir0
Wtnpl+TdAoGAGqKqo2KU3JoY3IuTDUk1dsNAm8jd9EWDh+s1x4aG4N79mwcss5GD
FF8MbFPneK7xQd8L6HisKUDAUi2NOyynM81LAftPkvN6ZuUVeFDfCL4vCA0HUXLD
+VrOhtUZkNNJlLMiVRJuQKUOGlg8PpObqYbstQAf/0/yFJMRHG82Tcg=
-----END RSA PRIVATE KEY-----`
+9 -2
View File
@@ -1,6 +1,13 @@
package encryption
import "context"
type Service interface {
Encrypt([]byte, string) ([]byte, error)
Decrypt([]byte, string) ([]byte, error)
Encrypt(ctx context.Context, payload []byte, secret string) ([]byte, error)
Decrypt(ctx context.Context, payload []byte, secret string) ([]byte, error)
EncryptJsonData(ctx context.Context, kv map[string]string, secret string) (map[string][]byte, error)
DecryptJsonData(ctx context.Context, sjd map[string][]byte, secret string) (map[string]string, error)
GetDecryptedValue(ctx context.Context, sjd map[string][]byte, key string, fallback string, secret string) string
}
@@ -1,6 +1,7 @@
package ossencryption
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
@@ -21,7 +22,7 @@ func ProvideService() *Service {
const saltLength = 8
func (s *Service) Decrypt(payload []byte, secret string) ([]byte, error) {
func (s *Service) Decrypt(_ context.Context, payload []byte, secret string) ([]byte, error) {
if len(payload) < saltLength {
return nil, fmt.Errorf("unable to compute salt")
}
@@ -52,7 +53,7 @@ func (s *Service) Decrypt(payload []byte, secret string) ([]byte, error) {
return payloadDst, nil
}
func (s *Service) Encrypt(payload []byte, secret string) ([]byte, error) {
func (s *Service) Encrypt(_ context.Context, payload []byte, secret string) ([]byte, error) {
salt, err := util.GetRandomString(saltLength)
if err != nil {
return nil, err
@@ -82,6 +83,45 @@ func (s *Service) Encrypt(payload []byte, secret string) ([]byte, error) {
return ciphertext, nil
}
func (s *Service) EncryptJsonData(ctx context.Context, kv map[string]string, secret string) (map[string][]byte, error) {
encrypted := make(map[string][]byte)
for key, value := range kv {
encryptedData, err := s.Encrypt(ctx, []byte(value), secret)
if err != nil {
return nil, err
}
encrypted[key] = encryptedData
}
return encrypted, nil
}
func (s *Service) DecryptJsonData(ctx context.Context, sjd map[string][]byte, secret string) (map[string]string, error) {
decrypted := make(map[string]string)
for key, data := range sjd {
decryptedData, err := s.Decrypt(ctx, data, secret)
if err != nil {
return nil, err
}
decrypted[key] = string(decryptedData)
}
return decrypted, nil
}
func (s *Service) GetDecryptedValue(ctx context.Context, sjd map[string][]byte, key, fallback, secret string) string {
if value, ok := sjd[key]; ok {
decryptedData, err := s.Decrypt(ctx, value, secret)
if err != nil {
return fallback
}
return string(decryptedData)
}
return fallback
}
// Key needs to be 32bytes
func encryptionKeyToBytes(secret, salt string) ([]byte, error) {
return pbkdf2.Key([]byte(secret), []byte(salt), 10000, 32, sha256.New), nil
@@ -1,6 +1,7 @@
package ossencryption
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
@@ -21,17 +22,19 @@ func TestEncryption(t *testing.T) {
})
t.Run("decrypting basic payload", func(t *testing.T) {
encrypted, err := svc.Encrypt([]byte("grafana"), "1234")
ctx := context.Background()
encrypted, err := svc.Encrypt(ctx, []byte("grafana"), "1234")
require.NoError(t, err)
decrypted, err := svc.Decrypt(encrypted, "1234")
decrypted, err := svc.Decrypt(ctx, encrypted, "1234")
require.NoError(t, err)
assert.Equal(t, []byte("grafana"), decrypted)
})
t.Run("decrypting empty payload should return error", func(t *testing.T) {
_, err := svc.Decrypt([]byte(""), "1234")
_, err := svc.Decrypt(context.Background(), []byte(""), "1234")
require.Error(t, err)
assert.Equal(t, "unable to compute salt", err.Error())
@@ -169,7 +169,7 @@ func (s *Implementation) decodeAndDecrypt(str string) (string, error) {
if err != nil {
return "", err
}
decrypted, err := s.EncryptionService.Decrypt(decoded, setting.SecretKey)
decrypted, err := s.EncryptionService.Decrypt(context.Background(), decoded, setting.SecretKey)
if err != nil {
return "", err
}
@@ -179,7 +179,7 @@ func (s *Implementation) decodeAndDecrypt(str string) (string, error) {
// encryptAndEncode will encrypt a string with grafana's secretKey, and
// then encode it with the standard bas64 encoder
func (s *Implementation) encryptAndEncode(str string) (string, error) {
encrypted, err := s.EncryptionService.Encrypt([]byte(str), setting.SecretKey)
encrypted, err := s.EncryptionService.Encrypt(context.Background(), []byte(str), setting.SecretKey)
if err != nil {
return "", err
}
+3 -1
View File
@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/encryption"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
@@ -63,6 +64,7 @@ type API struct {
DataProxy *datasourceproxy.DataSourceProxyService
MultiOrgAlertmanager *notifier.MultiOrgAlertmanager
StateManager *state.Manager
EncryptionService encryption.Service
}
// RegisterAPIEndpoints registers API handlers
@@ -76,7 +78,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
api.RegisterAlertmanagerApiEndpoints(NewForkedAM(
api.DatasourceCache,
NewLotexAM(proxy, logger),
AlertmanagerSrv{store: api.AlertingStore, mam: api.MultiOrgAlertmanager, log: logger},
AlertmanagerSrv{store: api.AlertingStore, mam: api.MultiOrgAlertmanager, enc: api.EncryptionService, log: logger},
), m)
// Register endpoints for proxying to Prometheus-compatible backends.
api.RegisterPrometheusApiEndpoints(NewForkedProm(
+27 -4
View File
@@ -2,6 +2,7 @@ package api
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net/http"
@@ -12,10 +13,12 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"gopkg.in/macaron.v1"
)
@@ -27,6 +30,7 @@ const (
type AlertmanagerSrv struct {
mam *notifier.MultiOrgAlertmanager
enc encryption.Service
store store.AlertingStore
log log.Logger
}
@@ -76,7 +80,7 @@ func (srv AlertmanagerSrv) loadSecureSettings(orgId int64, receivers []*apimodel
for key := range cgmr.SecureSettings {
_, ok := gr.SecureSettings[key]
if !ok {
decryptedValue, err := cgmr.GetDecryptedSecret(key)
decryptedValue, err := srv.getDecryptedSecret(cgmr, key)
if err != nil {
return fmt.Errorf("failed to decrypt stored secure setting: %s: %w", key, err)
}
@@ -93,6 +97,25 @@ func (srv AlertmanagerSrv) loadSecureSettings(orgId int64, receivers []*apimodel
return nil
}
func (srv AlertmanagerSrv) getDecryptedSecret(r *apimodels.PostableGrafanaReceiver, key string) (string, error) {
storedValue, ok := r.SecureSettings[key]
if !ok {
return "", nil
}
decodeValue, err := base64.StdEncoding.DecodeString(storedValue)
if err != nil {
return "", err
}
decryptedValue, err := srv.enc.Decrypt(context.Background(), decodeValue, setting.SecretKey)
if err != nil {
return "", err
}
return string(decryptedValue), nil
}
func (srv AlertmanagerSrv) RouteGetAMStatus(c *models.ReqContext) response.Response {
am, errResp := srv.AlertmanagerFor(c.OrgId)
if errResp != nil {
@@ -194,7 +217,7 @@ func (srv AlertmanagerSrv) RouteGetAlertingConfig(c *models.ReqContext) response
for _, pr := range recv.PostableGrafanaReceivers.GrafanaManagedReceivers {
secureFields := make(map[string]bool, len(pr.SecureSettings))
for k := range pr.SecureSettings {
decryptedValue, err := pr.GetDecryptedSecret(k)
decryptedValue, err := srv.getDecryptedSecret(pr, k)
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to decrypt stored secure setting: %s", k)
}
@@ -333,7 +356,7 @@ func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body ap
return ErrResp(http.StatusInternalServerError, err, "")
}
if err := body.ProcessConfig(); err != nil {
if err := body.ProcessConfig(srv.enc.Encrypt); err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to post process Alertmanager configuration")
}
@@ -367,7 +390,7 @@ func (srv AlertmanagerSrv) RoutePostTestReceivers(c *models.ReqContext, body api
return ErrResp(http.StatusInternalServerError, err, "")
}
if err := body.ProcessConfig(); err != nil {
if err := body.ProcessConfig(srv.enc.Encrypt); err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to post process Alertmanager configuration")
}
@@ -1,6 +1,7 @@
package definitions
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
@@ -9,16 +10,15 @@ import (
"time"
"github.com/go-openapi/strfmt"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/pkg/errors"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/common/model"
"gopkg.in/yaml.v3"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
// swagger:route POST /api/alertmanager/{Recipient}/config/api/v1/alerts alertmanager RoutePostAlertingConfig
@@ -141,8 +141,8 @@ type TestReceiversConfigParams struct {
Receivers []*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
}
func (c *TestReceiversConfigParams) ProcessConfig() error {
return processReceiverConfigs(c.Receivers)
func (c *TestReceiversConfigParams) ProcessConfig(encrypt EncryptFn) error {
return processReceiverConfigs(c.Receivers, encrypt)
}
// swagger:model
@@ -412,8 +412,8 @@ func (c *PostableUserConfig) GetGrafanaReceiverMap() map[string]*PostableGrafana
}
// ProcessConfig parses grafana receivers, encrypts secrets and assigns UUIDs (if they are missing)
func (c *PostableUserConfig) ProcessConfig() error {
return processReceiverConfigs(c.AlertmanagerConfig.Receivers)
func (c *PostableUserConfig) ProcessConfig(encrypt EncryptFn) error {
return processReceiverConfigs(c.AlertmanagerConfig.Receivers, encrypt)
}
// MarshalYAML implements yaml.Marshaller.
@@ -870,22 +870,6 @@ type PostableGrafanaReceiver struct {
SecureSettings map[string]string `json:"secureSettings"`
}
func (r *PostableGrafanaReceiver) GetDecryptedSecret(key string) (string, error) {
storedValue, ok := r.SecureSettings[key]
if !ok {
return "", nil
}
decodeValue, err := base64.StdEncoding.DecodeString(storedValue)
if err != nil {
return "", err
}
decryptedValue, err := util.Decrypt(decodeValue, setting.SecretKey)
if err != nil {
return "", err
}
return string(decryptedValue), nil
}
type ReceiverType int
const (
@@ -1061,7 +1045,9 @@ type PostableGrafanaReceivers struct {
GrafanaManagedReceivers []*PostableGrafanaReceiver `yaml:"grafana_managed_receiver_configs,omitempty" json:"grafana_managed_receiver_configs,omitempty"`
}
func processReceiverConfigs(c []*PostableApiReceiver) error {
type EncryptFn func(ctx context.Context, payload []byte, secret string) ([]byte, error)
func processReceiverConfigs(c []*PostableApiReceiver, encrypt EncryptFn) error {
seenUIDs := make(map[string]struct{})
// encrypt secure settings for storing them in DB
for _, r := range c {
@@ -1069,7 +1055,7 @@ func processReceiverConfigs(c []*PostableApiReceiver) error {
case GrafanaReceiverType:
for _, gr := range r.PostableGrafanaReceivers.GrafanaManagedReceivers {
for k, v := range gr.SecureSettings {
encryptedData, err := util.Encrypt([]byte(v), setting.SecretKey)
encryptedData, err := encrypt(context.Background(), []byte(v), setting.SecretKey)
if err != nil {
return fmt.Errorf("failed to encrypt secure settings: %w", err)
}
+30 -26
View File
@@ -5,11 +5,13 @@ import (
"net/url"
"time"
"github.com/benbjohnson/clock"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/ngalert/api"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
@@ -21,8 +23,6 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/benbjohnson/clock"
"golang.org/x/sync/errgroup"
)
@@ -39,18 +39,19 @@ const (
func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService, routeRegister routing.RouteRegister,
sqlStore *sqlstore.SQLStore, kvStore kvstore.KVStore, dataService *tsdb.Service, dataProxy *datasourceproxy.DataSourceProxyService,
quotaService *quota.QuotaService, m *metrics.NGAlert) (*AlertNG, error) {
quotaService *quota.QuotaService, encryptionService encryption.Service, m *metrics.NGAlert) (*AlertNG, error) {
ng := &AlertNG{
Cfg: cfg,
DataSourceCache: dataSourceCache,
RouteRegister: routeRegister,
SQLStore: sqlStore,
KVStore: kvStore,
DataService: dataService,
DataProxy: dataProxy,
QuotaService: quotaService,
Metrics: m,
Log: log.New("ngalert"),
Cfg: cfg,
DataSourceCache: dataSourceCache,
RouteRegister: routeRegister,
SQLStore: sqlStore,
KVStore: kvStore,
DataService: dataService,
DataProxy: dataProxy,
QuotaService: quotaService,
EncryptionService: encryptionService,
Metrics: m,
Log: log.New("ngalert"),
}
if ng.IsDisabled() {
@@ -66,18 +67,19 @@ func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService,
// AlertNG is the service for evaluating the condition of an alert definition.
type AlertNG struct {
Cfg *setting.Cfg
DataSourceCache datasources.CacheService
RouteRegister routing.RouteRegister
SQLStore *sqlstore.SQLStore
KVStore kvstore.KVStore
DataService *tsdb.Service
DataProxy *datasourceproxy.DataSourceProxyService
QuotaService *quota.QuotaService
Metrics *metrics.NGAlert
Log log.Logger
schedule schedule.ScheduleService
stateManager *state.Manager
Cfg *setting.Cfg
DataSourceCache datasources.CacheService
RouteRegister routing.RouteRegister
SQLStore *sqlstore.SQLStore
KVStore kvstore.KVStore
DataService *tsdb.Service
DataProxy *datasourceproxy.DataSourceProxyService
QuotaService *quota.QuotaService
EncryptionService encryption.Service
Metrics *metrics.NGAlert
Log log.Logger
schedule schedule.ScheduleService
stateManager *state.Manager
// Alerting notification services
MultiOrgAlertmanager *notifier.MultiOrgAlertmanager
@@ -99,8 +101,9 @@ func (ng *AlertNG) init() error {
Logger: ng.Log,
}
decryptFn := ng.EncryptionService.GetDecryptedValue
multiOrgMetrics := ng.Metrics.GetMultiOrgAlertmanagerMetrics()
ng.MultiOrgAlertmanager, err = notifier.NewMultiOrgAlertmanager(ng.Cfg, store, store, ng.KVStore, multiOrgMetrics, log.New("ngalert.multiorg.alertmanager"))
ng.MultiOrgAlertmanager, err = notifier.NewMultiOrgAlertmanager(ng.Cfg, store, store, ng.KVStore, decryptFn, multiOrgMetrics, log.New("ngalert.multiorg.alertmanager"))
if err != nil {
return err
}
@@ -146,6 +149,7 @@ func (ng *AlertNG) init() error {
Schedule: ng.schedule,
DataProxy: ng.DataProxy,
QuotaService: ng.QuotaService,
EncryptionService: ng.EncryptionService,
InstanceStore: store,
RuleStore: store,
AlertingStore: store,
+16 -13
View File
@@ -31,7 +31,6 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
@@ -118,9 +117,12 @@ type Alertmanager struct {
config *apimodels.PostableUserConfig
configHash [16]byte
orgID int64
decryptFn channels.GetDecryptedValueFn
}
func newAlertmanager(orgID int64, cfg *setting.Cfg, store store.AlertingStore, kvStore kvstore.KVStore, peer ClusterPeer, m *metrics.Alertmanager) (*Alertmanager, error) {
func newAlertmanager(orgID int64, cfg *setting.Cfg, store store.AlertingStore, kvStore kvstore.KVStore,
peer ClusterPeer, decryptFn channels.GetDecryptedValueFn, m *metrics.Alertmanager) (*Alertmanager, error) {
am := &Alertmanager{
Settings: cfg,
stopc: make(chan struct{}),
@@ -133,6 +135,7 @@ func newAlertmanager(orgID int64, cfg *setting.Cfg, store store.AlertingStore, k
peerTimeout: cfg.UnifiedAlerting.HAPeerTimeout,
Metrics: m,
orgID: orgID,
decryptFn: decryptFn,
}
am.gokitLogger = gokit_log.NewLogfmtLogger(logging.NewWrapper(am.logger))
@@ -472,7 +475,7 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp
func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaReceiver, tmpl *template.Template) (NotificationChannel, error) {
// secure settings are already encrypted at this point
secureSettings := securejsondata.SecureJsonData(make(map[string][]byte, len(r.SecureSettings)))
secureSettings := make(map[string][]byte, len(r.SecureSettings))
for k, v := range r.SecureSettings {
d, err := base64.StdEncoding.DecodeString(v)
@@ -501,13 +504,13 @@ func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaRec
case "email":
n, err = channels.NewEmailNotifier(cfg, tmpl) // Email notifier already has a default template.
case "pagerduty":
n, err = channels.NewPagerdutyNotifier(cfg, tmpl)
n, err = channels.NewPagerdutyNotifier(cfg, tmpl, am.decryptFn)
case "pushover":
n, err = channels.NewPushoverNotifier(cfg, tmpl)
n, err = channels.NewPushoverNotifier(cfg, tmpl, am.decryptFn)
case "slack":
n, err = channels.NewSlackNotifier(cfg, tmpl)
n, err = channels.NewSlackNotifier(cfg, tmpl, am.decryptFn)
case "telegram":
n, err = channels.NewTelegramNotifier(cfg, tmpl)
n, err = channels.NewTelegramNotifier(cfg, tmpl, am.decryptFn)
case "victorops":
n, err = channels.NewVictoropsNotifier(cfg, tmpl)
case "teams":
@@ -517,21 +520,21 @@ func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaRec
case "kafka":
n, err = channels.NewKafkaNotifier(cfg, tmpl)
case "webhook":
n, err = channels.NewWebHookNotifier(cfg, tmpl)
n, err = channels.NewWebHookNotifier(cfg, tmpl, am.decryptFn)
case "sensugo":
n, err = channels.NewSensuGoNotifier(cfg, tmpl)
n, err = channels.NewSensuGoNotifier(cfg, tmpl, am.decryptFn)
case "discord":
n, err = channels.NewDiscordNotifier(cfg, tmpl)
case "googlechat":
n, err = channels.NewGoogleChatNotifier(cfg, tmpl)
case "LINE":
n, err = channels.NewLineNotifier(cfg, tmpl)
n, err = channels.NewLineNotifier(cfg, tmpl, am.decryptFn)
case "threema":
n, err = channels.NewThreemaNotifier(cfg, tmpl)
n, err = channels.NewThreemaNotifier(cfg, tmpl, am.decryptFn)
case "opsgenie":
n, err = channels.NewOpsgenieNotifier(cfg, tmpl)
n, err = channels.NewOpsgenieNotifier(cfg, tmpl, am.decryptFn)
case "prometheus-alertmanager":
n, err = channels.NewAlertmanagerNotifier(cfg, tmpl)
n, err = channels.NewAlertmanagerNotifier(cfg, tmpl, am.decryptFn)
default:
return nil, InvalidReceiverError{
Receiver: r,
@@ -9,23 +9,22 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/infra/log"
gokit_log "github.com/go-kit/kit/log"
"github.com/go-openapi/strfmt"
"github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/provider/mem"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/logging"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/provider/mem"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)
func setupAMTest(t *testing.T) *Alertmanager {
@@ -48,7 +47,8 @@ func setupAMTest(t *testing.T) *Alertmanager {
}
kvStore := newFakeKVStore(t)
am, err := newAlertmanager(1, cfg, s, kvStore, &NilPeer{}, m)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
am, err := newAlertmanager(1, cfg, s, kvStore, &NilPeer{}, decryptFn, m)
require.NoError(t, err)
return am
}
@@ -10,12 +10,17 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
)
// GetDecryptedValueFn is a function that returns the decrypted value of
// the given key. If the key is not present, then it returns the fallback value.
type GetDecryptedValueFn func(ctx context.Context, sjd map[string][]byte, key string, fallback string, secret string) string
// NewAlertmanagerNotifier returns a new Alertmanager notifier.
func NewAlertmanagerNotifier(model *NotificationChannelConfig, t *template.Template) (*AlertmanagerNotifier, error) {
func NewAlertmanagerNotifier(model *NotificationChannelConfig, _ *template.Template, fn GetDecryptedValueFn) (*AlertmanagerNotifier, error) {
if model.Settings == nil {
return nil, receiverInitError{Reason: "no settings supplied"}
}
@@ -41,7 +46,7 @@ func NewAlertmanagerNotifier(model *NotificationChannelConfig, t *template.Templ
urls = append(urls, u)
}
basicAuthUser := model.Settings.Get("basicAuthUser").MustString()
basicAuthPassword := model.DecryptedValue("basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString())
basicAuthPassword := fn(context.Background(), model.SecureSettings, "basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString(), setting.SecretKey)
return &AlertmanagerNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
@@ -6,6 +6,8 @@ import (
"net/url"
"testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
@@ -77,7 +79,8 @@ func TestAlertmanagerNotifier(t *testing.T) {
Settings: settingsJSON,
}
sn, err := NewAlertmanagerNotifier(m, tmpl)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
sn, err := NewAlertmanagerNotifier(m, tmpl, decryptFn)
if c.expInitError != "" {
require.Equal(t, c.expInitError, err.Error())
return
@@ -6,13 +6,13 @@ import (
"net/url"
"path"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
)
var (
@@ -20,8 +20,8 @@ var (
)
// NewLineNotifier is the constructor for the LINE notifier
func NewLineNotifier(model *NotificationChannelConfig, t *template.Template) (*LineNotifier, error) {
token := model.DecryptedValue("token", model.Settings.Get("token").MustString())
func NewLineNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*LineNotifier, error) {
token := fn(context.Background(), model.SecureSettings, "token", model.Settings.Get("token").MustString(), setting.SecretKey)
if token == "" {
return nil, receiverInitError{Cfg: *model, Reason: "could not find token in settings"}
}
@@ -5,6 +5,8 @@ import (
"net/url"
"testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
@@ -88,7 +90,8 @@ func TestLineNotifier(t *testing.T) {
Settings: settingsJSON,
}
pn, err := NewLineNotifier(m, tmpl)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewLineNotifier(m, tmpl, decryptFn)
if c.expInitError != "" {
require.Error(t, err)
require.Equal(t, c.expInitError, err.Error())
@@ -7,16 +7,16 @@ import (
"net/http"
"sort"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
)
const (
@@ -43,10 +43,10 @@ type OpsgenieNotifier struct {
}
// NewOpsgenieNotifier is the constructor for the Opsgenie notifier
func NewOpsgenieNotifier(model *NotificationChannelConfig, t *template.Template) (*OpsgenieNotifier, error) {
func NewOpsgenieNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*OpsgenieNotifier, error) {
autoClose := model.Settings.Get("autoClose").MustBool(true)
overridePriority := model.Settings.Get("overridePriority").MustBool(true)
apiKey := model.DecryptedValue("apiKey", model.Settings.Get("apiKey").MustString())
apiKey := fn(context.Background(), model.SecureSettings, "apiKey", model.Settings.Get("apiKey").MustString(), setting.SecretKey)
apiURL := model.Settings.Get("apiUrl").MustString()
if apiKey == "" {
return nil, receiverInitError{Cfg: *model, Reason: "could not find api key property in settings"}
@@ -6,6 +6,8 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
@@ -168,7 +170,8 @@ func TestOpsgenieNotifier(t *testing.T) {
Settings: settingsJSON,
}
pn, err := NewOpsgenieNotifier(m, tmpl)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewOpsgenieNotifier(m, tmpl, decryptFn)
if c.expInitError != "" {
require.Error(t, err)
require.Equal(t, c.expInitError, err.Error())
@@ -6,15 +6,15 @@ import (
"fmt"
"os"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
)
const (
@@ -42,12 +42,12 @@ type PagerdutyNotifier struct {
}
// NewPagerdutyNotifier is the constructor for the PagerDuty notifier
func NewPagerdutyNotifier(model *NotificationChannelConfig, t *template.Template) (*PagerdutyNotifier, error) {
func NewPagerdutyNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*PagerdutyNotifier, error) {
if model.Settings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"}
}
key := model.DecryptedValue("integrationKey", model.Settings.Get("integrationKey").MustString())
key := fn(context.Background(), model.SecureSettings, "integrationKey", model.Settings.Get("integrationKey").MustString(), setting.SecretKey)
if key == "" {
return nil, receiverInitError{Cfg: *model, Reason: "could not find integration key property in settings"}
}
@@ -7,6 +7,8 @@ import (
"os"
"testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
@@ -134,7 +136,8 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON,
}
pn, err := NewPagerdutyNotifier(m, tmpl)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewPagerdutyNotifier(m, tmpl, decryptFn)
if c.expInitError != "" {
require.Error(t, err)
require.Equal(t, c.expInitError, err.Error())
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
@@ -40,13 +41,13 @@ type PushoverNotifier struct {
}
// NewSlackNotifier is the constructor for the Slack notifier
func NewPushoverNotifier(model *NotificationChannelConfig, t *template.Template) (*PushoverNotifier, error) {
func NewPushoverNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*PushoverNotifier, error) {
if model.Settings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"}
}
userKey := model.DecryptedValue("userKey", model.Settings.Get("userKey").MustString())
APIToken := model.DecryptedValue("apiToken", model.Settings.Get("apiToken").MustString())
userKey := fn(context.Background(), model.SecureSettings, "userKey", model.Settings.Get("userKey").MustString(), setting.SecretKey)
APIToken := fn(context.Background(), model.SecureSettings, "apiToken", model.Settings.Get("apiToken").MustString(), setting.SecretKey)
device := model.Settings.Get("device").MustString()
alertingPriority, err := strconv.Atoi(model.Settings.Get("priority").MustString("0")) // default Normal
if err != nil {
@@ -11,6 +11,8 @@ import (
"strings"
"testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
@@ -141,7 +143,8 @@ func TestPushoverNotifier(t *testing.T) {
Settings: settingsJSON,
}
pn, err := NewPushoverNotifier(m, tmpl)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewPushoverNotifier(m, tmpl, decryptFn)
if c.expInitError != "" {
require.Error(t, err)
require.Equal(t, c.expInitError, err.Error())
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
@@ -31,7 +32,7 @@ type SensuGoNotifier struct {
}
// NewSensuGoNotifier is the constructor for the SensuGo notifier
func NewSensuGoNotifier(model *NotificationChannelConfig, t *template.Template) (*SensuGoNotifier, error) {
func NewSensuGoNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*SensuGoNotifier, error) {
if model.Settings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"}
}
@@ -41,7 +42,7 @@ func NewSensuGoNotifier(model *NotificationChannelConfig, t *template.Template)
return nil, receiverInitError{Cfg: *model, Reason: "could not find URL property in settings"}
}
apikey := model.DecryptedValue("apikey", model.Settings.Get("apikey").MustString())
apikey := fn(context.Background(), model.SecureSettings, "apikey", model.Settings.Get("apikey").MustString(), setting.SecretKey)
if apikey == "" {
return nil, receiverInitError{Cfg: *model, Reason: "could not find the API key property in settings"}
}
@@ -7,6 +7,8 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
@@ -139,7 +141,8 @@ func TestSensuGoNotifier(t *testing.T) {
Settings: settingsJSON,
}
sn, err := NewSensuGoNotifier(m, tmpl)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
sn, err := NewSensuGoNotifier(m, tmpl, decryptFn)
if c.expInitError != "" {
require.Error(t, err)
require.Equal(t, c.expInitError, err.Error())
@@ -14,14 +14,13 @@ import (
"strings"
"time"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
)
// SlackNotifier is responsible for sending
@@ -49,12 +48,12 @@ var reRecipient *regexp.Regexp = regexp.MustCompile("^((@[a-z0-9][a-zA-Z0-9._-]*
var SlackAPIEndpoint = "https://slack.com/api/chat.postMessage"
// NewSlackNotifier is the constructor for the Slack notifier
func NewSlackNotifier(model *NotificationChannelConfig, t *template.Template) (*SlackNotifier, error) {
func NewSlackNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*SlackNotifier, error) {
if model.Settings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"}
}
slackURL := model.DecryptedValue("url", model.Settings.Get("url").MustString())
slackURL := fn(context.Background(), model.SecureSettings, "url", model.Settings.Get("url").MustString(), setting.SecretKey)
if slackURL == "" {
slackURL = SlackAPIEndpoint
}
@@ -99,7 +98,7 @@ func NewSlackNotifier(model *NotificationChannelConfig, t *template.Template) (*
}
}
token := model.DecryptedValue("token", model.Settings.Get("token").MustString())
token := fn(context.Background(), model.SecureSettings, "token", model.Settings.Get("token").MustString(), setting.SecretKey)
if token == "" && apiURL.String() == SlackAPIEndpoint {
return nil, receiverInitError{Cfg: *model,
Reason: "token must be specified when using the Slack chat API",
@@ -8,6 +8,8 @@ import (
"net/url"
"testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
@@ -169,7 +171,8 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON,
}
pn, err := NewSlackNotifier(m, tmpl)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewSlackNotifier(m, tmpl, decryptFn)
if c.expInitError != "" {
require.Error(t, err)
require.Equal(t, c.expInitError, err.Error())
@@ -6,13 +6,13 @@ import (
"fmt"
"mime/multipart"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
)
var (
@@ -31,12 +31,12 @@ type TelegramNotifier struct {
}
// NewTelegramNotifier is the constructor for the Telegram notifier
func NewTelegramNotifier(model *NotificationChannelConfig, t *template.Template) (*TelegramNotifier, error) {
func NewTelegramNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*TelegramNotifier, error) {
if model.Settings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"}
}
botToken := model.DecryptedValue("bottoken", model.Settings.Get("bottoken").MustString())
botToken := fn(context.Background(), model.SecureSettings, "bottoken", model.Settings.Get("bottoken").MustString(), setting.SecretKey)
chatID := model.Settings.Get("chatid").MustString()
message := model.Settings.Get("message").MustString(`{{ template "default.message" . }}`)
@@ -5,6 +5,8 @@ import (
"net/url"
"testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
@@ -94,7 +96,8 @@ func TestTelegramNotifier(t *testing.T) {
Settings: settingsJSON,
}
pn, err := NewTelegramNotifier(m, tmpl)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewTelegramNotifier(m, tmpl, decryptFn)
if c.expInitError != "" {
require.Error(t, err)
require.Equal(t, c.expInitError, err.Error())
@@ -7,14 +7,14 @@ import (
"path"
"strings"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
)
var (
@@ -33,14 +33,14 @@ type ThreemaNotifier struct {
}
// NewThreemaNotifier is the constructor for the Threema notifier
func NewThreemaNotifier(model *NotificationChannelConfig, t *template.Template) (*ThreemaNotifier, error) {
func NewThreemaNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*ThreemaNotifier, error) {
if model.Settings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"}
}
gatewayID := model.Settings.Get("gateway_id").MustString()
recipientID := model.Settings.Get("recipient_id").MustString()
apiSecret := model.DecryptedValue("api_secret", model.Settings.Get("api_secret").MustString())
apiSecret := fn(context.Background(), model.SecureSettings, "api_secret", model.Settings.Get("api_secret").MustString(), setting.SecretKey)
// Validation
if gatewayID == "" {
@@ -5,6 +5,8 @@ import (
"net/url"
"testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
@@ -106,7 +108,8 @@ func TestThreemaNotifier(t *testing.T) {
Settings: settingsJSON,
}
pn, err := NewThreemaNotifier(m, tmpl)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewThreemaNotifier(m, tmpl, decryptFn)
if c.expInitError != "" {
require.Error(t, err)
require.Equal(t, c.expInitError, err.Error())
@@ -16,7 +16,6 @@ import (
"github.com/grafana/grafana/pkg/util"
"github.com/prometheus/common/model"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson"
)
@@ -56,20 +55,12 @@ func getAlertStatusColor(status model.AlertStatus) string {
}
type NotificationChannelConfig struct {
UID string `json:"uid"`
Name string `json:"name"`
Type string `json:"type"`
DisableResolveMessage bool `json:"disableResolveMessage"`
Settings *simplejson.Json `json:"settings"`
SecureSettings securejsondata.SecureJsonData `json:"secureSettings"`
}
// DecryptedValue returns decrypted value from secureSettings
func (an *NotificationChannelConfig) DecryptedValue(field string, fallback string) string {
if value, ok := an.SecureSettings.DecryptedValue(field); ok {
return value
}
return fallback
UID string `json:"uid"`
Name string `json:"name"`
Type string `json:"type"`
DisableResolveMessage bool `json:"disableResolveMessage"`
Settings *simplejson.Json `json:"settings"`
SecureSettings map[string][]byte `json:"secureSettings"`
}
type httpCfg struct {
@@ -4,15 +4,15 @@ import (
"context"
"encoding/json"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
)
// WebhookNotifier is responsible for sending
@@ -30,7 +30,7 @@ type WebhookNotifier struct {
// NewWebHookNotifier is the constructor for
// the WebHook notifier.
func NewWebHookNotifier(model *NotificationChannelConfig, t *template.Template) (*WebhookNotifier, error) {
func NewWebHookNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*WebhookNotifier, error) {
if model.Settings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "could not find settings property"}
}
@@ -48,7 +48,7 @@ func NewWebHookNotifier(model *NotificationChannelConfig, t *template.Template)
}),
URL: url,
User: model.Settings.Get("username").MustString(),
Password: model.DecryptedValue("password", model.Settings.Get("password").MustString()),
Password: fn(context.Background(), model.SecureSettings, "password", model.Settings.Get("password").MustString(), setting.SecretKey),
HTTPMethod: model.Settings.Get("httpMethod").MustString("POST"),
MaxAlerts: model.Settings.Get("maxAlerts").MustInt(0),
log: log.New("alerting.notifier.webhook"),
@@ -6,6 +6,8 @@ import (
"net/url"
"testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
@@ -183,7 +185,8 @@ func TestWebhookNotifier(t *testing.T) {
Settings: settingsJSON,
}
pn, err := NewWebHookNotifier(m, tmpl)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewWebHookNotifier(m, tmpl, decryptFn)
if c.expInitError != "" {
require.Error(t, err)
require.Equal(t, c.expInitError, err.Error())
@@ -6,18 +6,18 @@ import (
"sync"
"time"
"github.com/grafana/grafana/pkg/services/ngalert/logging"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels"
gokit_log "github.com/go-kit/kit/log"
"github.com/prometheus/alertmanager/cluster"
"github.com/prometheus/client_golang/prometheus"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/ngalert/logging"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/cluster"
"github.com/prometheus/client_golang/prometheus"
)
var (
@@ -40,10 +40,14 @@ type MultiOrgAlertmanager struct {
orgStore store.OrgStore
kvStore kvstore.KVStore
decryptFn channels.GetDecryptedValueFn
metrics *metrics.MultiOrgAlertmanager
}
func NewMultiOrgAlertmanager(cfg *setting.Cfg, configStore store.AlertingStore, orgStore store.OrgStore, kvStore kvstore.KVStore, m *metrics.MultiOrgAlertmanager, l log.Logger) (*MultiOrgAlertmanager, error) {
func NewMultiOrgAlertmanager(cfg *setting.Cfg, configStore store.AlertingStore, orgStore store.OrgStore,
kvStore kvstore.KVStore, decryptFn channels.GetDecryptedValueFn, m *metrics.MultiOrgAlertmanager, l log.Logger,
) (*MultiOrgAlertmanager, error) {
moa := &MultiOrgAlertmanager{
logger: l,
settings: cfg,
@@ -51,6 +55,7 @@ func NewMultiOrgAlertmanager(cfg *setting.Cfg, configStore store.AlertingStore,
configStore: configStore,
orgStore: orgStore,
kvStore: kvStore,
decryptFn: decryptFn,
metrics: m,
}
@@ -162,7 +167,7 @@ func (moa *MultiOrgAlertmanager) SyncAlertmanagersForOrgs(ctx context.Context, o
// To export them, we need to translate the metrics from each individual registry and,
// then aggregate them on the main registry.
m := metrics.NewAlertmanagerMetrics(moa.metrics.GetOrCreateOrgRegistry(orgID))
am, err := newAlertmanager(orgID, moa.settings, moa.configStore, moa.kvStore, moa.peer, m)
am, err := newAlertmanager(orgID, moa.settings, moa.configStore, moa.kvStore, moa.peer, moa.decryptFn, m)
if err != nil {
moa.logger.Error("unable to create Alertmanager for org", "org", orgID, "err", err)
}
@@ -9,10 +9,10 @@ import (
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/require"
@@ -29,6 +29,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgs(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "test")
require.NoError(t, err)
kvStore := newFakeKVStore(t)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
reg := prometheus.NewPedanticRegistry()
m := metrics.NewNGAlert(reg)
cfg := &setting.Cfg{
@@ -39,7 +40,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgs(t *testing.T) {
DisabledOrgs: map[int64]struct{}{5: {}},
}, // do not poll in tests.
}
mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, m.GetMultiOrgAlertmanagerMetrics(), log.New("testlogger"))
mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), log.New("testlogger"))
require.NoError(t, err)
ctx := context.Background()
@@ -108,9 +109,10 @@ func TestMultiOrgAlertmanager_AlertmanagerFor(t *testing.T) {
UnifiedAlerting: setting.UnifiedAlertingSettings{AlertmanagerConfigPollInterval: 3 * time.Minute, DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration()}, // do not poll in tests.
}
kvStore := newFakeKVStore(t)
decryptFn := ossencryption.ProvideService().GetDecryptedValue
reg := prometheus.NewPedanticRegistry()
m := metrics.NewNGAlert(reg)
mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, m.GetMultiOrgAlertmanagerMetrics(), log.New("testlogger"))
mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), log.New("testlogger"))
require.NoError(t, err)
ctx := context.Background()
@@ -9,7 +9,9 @@ import (
"testing"
"time"
"github.com/benbjohnson/clock"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
@@ -18,8 +20,6 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/state"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/setting"
"github.com/benbjohnson/clock"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
@@ -232,8 +232,10 @@ func setupScheduler(t *testing.T, rs store.RuleStore, is store.InstanceStore, ac
mockedClock := clock.NewMock()
logger := log.New("ngalert schedule test")
m := metrics.NewNGAlert(prometheus.NewPedanticRegistry())
moa, err := notifier.NewMultiOrgAlertmanager(&setting.Cfg{}, &notifier.FakeConfigStore{}, &notifier.FakeOrgStore{}, &notifier.FakeKVStore{}, nil, log.New("testlogger"))
decryptFn := ossencryption.ProvideService().GetDecryptedValue
moa, err := notifier.NewMultiOrgAlertmanager(&setting.Cfg{}, &notifier.FakeConfigStore{}, &notifier.FakeOrgStore{}, &notifier.FakeKVStore{}, decryptFn, nil, log.New("testlogger"))
require.NoError(t, err)
schedCfg := SchedulerCfg{
C: mockedClock,
BaseInterval: time.Second,
+8 -7
View File
@@ -9,18 +9,16 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/ngalert"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)
@@ -34,7 +32,10 @@ func SetupTestEnv(t *testing.T, baseInterval time.Duration) (*ngalert.AlertNG, *
cfg.UnifiedAlerting.Enabled = true
m := metrics.NewNGAlert(prometheus.NewRegistry())
ng, err := ngalert.ProvideService(cfg, nil, routing.NewRouteRegister(), sqlstore.InitTestDB(t), nil, nil, nil, nil, m)
ng, err := ngalert.ProvideService(
cfg, nil, routing.NewRouteRegister(), sqlstore.InitTestDB(t),
nil, nil, nil, nil, ossencryption.ProvideService(), m,
)
require.NoError(t, err)
return ng, &store.DBstore{
SQLStore: ng.SQLStore,
+91
View File
@@ -0,0 +1,91 @@
package pluginsettings
import (
"context"
"sync"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
)
type Service struct {
Bus bus.Bus
SQLStore *sqlstore.SQLStore
EncryptionService encryption.Service
logger log.Logger
pluginSettingDecryptionCache secureJSONDecryptionCache
}
type cachedDecryptedJSON struct {
updated time.Time
json map[string]string
}
type secureJSONDecryptionCache struct {
cache map[int64]cachedDecryptedJSON
sync.Mutex
}
func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, encryptionService encryption.Service) *Service {
s := &Service{
Bus: bus,
SQLStore: store,
EncryptionService: encryptionService,
logger: log.New("pluginsettings"),
pluginSettingDecryptionCache: secureJSONDecryptionCache{
cache: make(map[int64]cachedDecryptedJSON),
},
}
s.Bus.AddHandler(s.GetPluginSettingById)
s.Bus.AddHandlerCtx(s.UpdatePluginSetting)
s.Bus.AddHandler(s.UpdatePluginSettingVersion)
return s
}
func (s *Service) GetPluginSettingById(query *models.GetPluginSettingByIdQuery) error {
return s.SQLStore.GetPluginSettingById(query)
}
func (s *Service) UpdatePluginSetting(ctx context.Context, cmd *models.UpdatePluginSettingCmd) error {
var err error
cmd.EncryptedSecureJsonData, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureJsonData, setting.SecretKey)
if err != nil {
return err
}
return s.SQLStore.UpdatePluginSetting(cmd)
}
func (s *Service) UpdatePluginSettingVersion(cmd *models.UpdatePluginSettingVersionCmd) error {
return s.SQLStore.UpdatePluginSettingVersion(cmd)
}
func (s *Service) DecryptedValues(ps *models.PluginSetting) map[string]string {
s.pluginSettingDecryptionCache.Lock()
defer s.pluginSettingDecryptionCache.Unlock()
if item, present := s.pluginSettingDecryptionCache.cache[ps.Id]; present && ps.Updated.Equal(item.updated) {
return item.json
}
json, err := s.EncryptionService.DecryptJsonData(context.Background(), ps.SecureJsonData, setting.SecretKey)
if err != nil {
s.logger.Error("Failed to decrypt secure json data", "error", err)
return map[string]string{}
}
s.pluginSettingDecryptionCache.cache[ps.Id] = cachedDecryptedJSON{
updated: ps.Updated,
json: json,
}
return json
}
@@ -0,0 +1,92 @@
package pluginsettings
import (
"context"
"testing"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
)
func TestService_DecryptedValuesCache(t *testing.T) {
t.Run("When plugin settings hasn't been updated, encrypted JSON should be fetched from cache", func(t *testing.T) {
ctx := context.Background()
encryptionService := ossencryption.ProvideService()
psService := ProvideService(bus.New(), nil, encryptionService)
encryptedJsonData, err := encryptionService.EncryptJsonData(
ctx,
map[string]string{
"password": "password",
}, setting.SecretKey)
require.NoError(t, err)
ps := models.PluginSetting{
Id: 1,
JsonData: map[string]interface{}{},
SecureJsonData: encryptedJsonData,
}
// Populate cache
password, ok := psService.DecryptedValues(&ps)["password"]
require.Equal(t, "password", password)
require.True(t, ok)
encryptedJsonData, err = encryptionService.EncryptJsonData(
ctx,
map[string]string{
"password": "",
}, setting.SecretKey)
require.NoError(t, err)
ps.SecureJsonData = encryptedJsonData
password, ok = psService.DecryptedValues(&ps)["password"]
require.Equal(t, "password", password)
require.True(t, ok)
})
t.Run("When plugin settings is updated, encrypted JSON should not be fetched from cache", func(t *testing.T) {
ctx := context.Background()
encryptionService := ossencryption.ProvideService()
psService := ProvideService(bus.New(), nil, encryptionService)
encryptedJsonData, err := encryptionService.EncryptJsonData(
ctx,
map[string]string{
"password": "password",
}, setting.SecretKey)
require.NoError(t, err)
ps := models.PluginSetting{
Id: 1,
JsonData: map[string]interface{}{},
SecureJsonData: encryptedJsonData,
}
// Populate cache
password, ok := psService.DecryptedValues(&ps)["password"]
require.Equal(t, "password", password)
require.True(t, ok)
encryptedJsonData, err = encryptionService.EncryptJsonData(
ctx,
map[string]string{
"password": "",
}, setting.SecretKey)
require.NoError(t, err)
ps.SecureJsonData = encryptedJsonData
ps.Updated = time.Now()
password, ok = psService.DecryptedValues(&ps)["password"]
require.Empty(t, password)
require.True(t, ok)
})
}
@@ -4,11 +4,12 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption"
)
// Provision alert notifiers
func Provision(configDirectory string) error {
dc := newNotificationProvisioner(log.New("provisioning.notifiers"))
func Provision(configDirectory string, encryptionService encryption.Service) error {
dc := newNotificationProvisioner(encryptionService, log.New("provisioning.notifiers"))
return dc.applyChanges(configDirectory)
}
@@ -18,10 +19,13 @@ type NotificationProvisioner struct {
cfgProvider *configReader
}
func newNotificationProvisioner(log log.Logger) NotificationProvisioner {
func newNotificationProvisioner(encryptionService encryption.Service, log log.Logger) NotificationProvisioner {
return NotificationProvisioner{
log: log,
cfgProvider: &configReader{log: log},
log: log,
cfgProvider: &configReader{
encryptionService: encryptionService,
log: log,
},
}
}
@@ -1,22 +1,25 @@
package notifiers
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/provisioning/utils"
"github.com/grafana/grafana/pkg/setting"
"gopkg.in/yaml.v2"
)
type configReader struct {
log log.Logger
encryptionService encryption.Service
log log.Logger
}
func (cr *configReader) readConfig(path string) ([]*notificationsAsConfig, error) {
@@ -44,15 +47,15 @@ func (cr *configReader) readConfig(path string) ([]*notificationsAsConfig, error
}
cr.log.Debug("Validating alert notifications")
if err = validateRequiredField(notifications); err != nil {
if err = cr.validateRequiredField(notifications); err != nil {
return nil, err
}
if err := checkOrgIDAndOrgName(notifications); err != nil {
if err := cr.checkOrgIDAndOrgName(notifications); err != nil {
return nil, err
}
if err := validateNotifications(notifications); err != nil {
if err := cr.validateNotifications(notifications); err != nil {
return nil, err
}
@@ -78,7 +81,7 @@ func (cr *configReader) parseNotificationConfig(path string, file os.FileInfo) (
return cfg.mapToNotificationFromConfig(), nil
}
func checkOrgIDAndOrgName(notifications []*notificationsAsConfig) error {
func (cr *configReader) checkOrgIDAndOrgName(notifications []*notificationsAsConfig) error {
for i := range notifications {
for _, notification := range notifications[i].Notifications {
if notification.OrgID < 1 {
@@ -107,7 +110,7 @@ func checkOrgIDAndOrgName(notifications []*notificationsAsConfig) error {
return nil
}
func validateRequiredField(notifications []*notificationsAsConfig) error {
func (cr *configReader) validateRequiredField(notifications []*notificationsAsConfig) error {
for i := range notifications {
var errStrings []string
for index, notification := range notifications[i].Notifications {
@@ -150,19 +153,29 @@ func validateRequiredField(notifications []*notificationsAsConfig) error {
return nil
}
func validateNotifications(notifications []*notificationsAsConfig) error {
func (cr *configReader) validateNotifications(notifications []*notificationsAsConfig) error {
for i := range notifications {
if notifications[i].Notifications == nil {
continue
}
for _, notification := range notifications[i].Notifications {
_, err := alerting.InitNotifier(&models.AlertNotification{
encryptedSecureSettings, err := cr.encryptionService.EncryptJsonData(
context.Background(),
notification.SecureSettings,
setting.SecretKey,
)
if err != nil {
return err
}
_, err = alerting.InitNotifier(&models.AlertNotification{
Name: notification.Name,
Settings: notification.SettingsToJSON(),
SecureSettings: securejsondata.GetEncryptedJsonData(notification.SecureSettings),
SecureSettings: encryptedSecureSettings,
Type: notification.Type,
})
}, cr.encryptionService.GetDecryptedValue)
if err != nil {
return err
@@ -5,10 +5,12 @@ import (
"os"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/sqlstore"
. "github.com/smartystreets/goconvey/convey"
)
@@ -30,7 +32,8 @@ func TestNotificationAsConfig(t *testing.T) {
logger := log.New("fake.log")
Convey("Testing notification as configuration", t, func() {
sqlstore.InitTestDB(t)
sqlStore := sqlstore.InitTestDB(t)
setupBusHandlers(sqlStore)
for i := 1; i < 5; i++ {
orgCommand := models.CreateOrgCommand{Name: fmt.Sprintf("Main Org. %v", i)}
@@ -52,7 +55,11 @@ func TestNotificationAsConfig(t *testing.T) {
Convey("Can read correct properties", func() {
_ = os.Setenv("TEST_VAR", "default")
cfgProvider := &configReader{log: log.New("test logger")}
cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"),
}
cfg, err := cfgProvider.readConfig(correctProperties)
_ = os.Unsetenv("TEST_VAR")
if err != nil {
@@ -125,13 +132,14 @@ func TestNotificationAsConfig(t *testing.T) {
Convey("One configured notification", func() {
Convey("no notification in database", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(twoNotificationsConfig)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 2)
@@ -144,22 +152,22 @@ func TestNotificationAsConfig(t *testing.T) {
Uid: "notifier1",
Type: "slack",
}
err := sqlstore.CreateAlertNotificationCommand(&existingNotificationCmd)
err := sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil)
So(existingNotificationCmd.Result, ShouldNotBeNil)
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 1)
Convey("should update one notification", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err = dc.applyChanges(twoNotificationsConfig)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 2)
@@ -177,12 +185,12 @@ func TestNotificationAsConfig(t *testing.T) {
})
})
Convey("Two notifications with is_default", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(doubleNotificationsConfig)
Convey("should both be inserted", func() {
So(err, ShouldBeNil)
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 2)
@@ -201,7 +209,7 @@ func TestNotificationAsConfig(t *testing.T) {
Uid: "notifier0",
Type: "slack",
}
err := sqlstore.CreateAlertNotificationCommand(&existingNotificationCmd)
err := sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil)
existingNotificationCmd = models.CreateAlertNotificationCommand{
Name: "channel3",
@@ -209,23 +217,23 @@ func TestNotificationAsConfig(t *testing.T) {
Uid: "notifier3",
Type: "slack",
}
err = sqlstore.CreateAlertNotificationCommand(&existingNotificationCmd)
err = sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil)
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 2)
Convey("should have two new notifications", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(twoNotificationsConfig)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
notificationsQuery = models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 4)
@@ -249,17 +257,17 @@ func TestNotificationAsConfig(t *testing.T) {
Uid: "notifier2",
Type: "slack",
}
err = sqlstore.CreateAlertNotificationCommand(&existingNotificationCmd)
err = sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil)
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err = dc.applyChanges(correctPropertiesWithOrgName)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: existingOrg2.Result.Id}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 1)
@@ -270,7 +278,7 @@ func TestNotificationAsConfig(t *testing.T) {
})
Convey("Config doesn't contain required field", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(noRequiredFields)
So(err, ShouldNotBeNil)
@@ -283,26 +291,34 @@ func TestNotificationAsConfig(t *testing.T) {
Convey("Empty yaml file", func() {
Convey("should have not changed repo", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(emptyFile)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldBeEmpty)
})
})
Convey("Broken yaml should return error", func() {
reader := &configReader{log: log.New("test logger")}
reader := &configReader{
encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"),
}
_, err := reader.readConfig(brokenYaml)
So(err, ShouldNotBeNil)
})
Convey("Skip invalid directory", func() {
cfgProvider := &configReader{log: log.New("test logger")}
cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"),
}
cfg, err := cfgProvider.readConfig(emptyFolder)
if err != nil {
t.Fatalf("readConfig return an error %v", err)
@@ -311,17 +327,53 @@ func TestNotificationAsConfig(t *testing.T) {
})
Convey("Unknown notifier should return error", func() {
cfgProvider := &configReader{log: log.New("test logger")}
cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"),
}
_, err := cfgProvider.readConfig(unknownNotifier)
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, `unsupported notification type "nonexisting"`)
})
Convey("Read incorrect properties", func() {
cfgProvider := &configReader{log: log.New("test logger")}
cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"),
}
_, err := cfgProvider.readConfig(incorrectSettings)
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "alert validation error: token must be specified when using the Slack chat API")
})
})
}
func setupBusHandlers(sqlStore *sqlstore.SQLStore) {
bus.AddHandler("getOrg", func(q *models.GetOrgByNameQuery) error {
return sqlstore.GetOrgByName(q)
})
bus.AddHandler("getAlertNotifications", func(q *models.GetAlertNotificationsWithUidQuery) error {
return sqlStore.GetAlertNotificationsWithUid(q)
})
bus.AddHandler("createAlertNotification", func(cmd *models.CreateAlertNotificationCommand) error {
return sqlStore.CreateAlertNotificationCommand(cmd)
})
bus.AddHandler("updateAlertNotification", func(cmd *models.UpdateAlertNotificationCommand) error {
return sqlStore.UpdateAlertNotification(cmd)
})
bus.AddHandler("updateAlertNotification", func(cmd *models.UpdateAlertNotificationWithUidCommand) error {
return sqlStore.UpdateAlertNotificationWithUid(cmd)
})
bus.AddHandler("deleteAlertNotification", func(cmd *models.DeleteAlertNotificationCommand) error {
return sqlStore.DeleteAlertNotification(cmd)
})
bus.AddHandler("deleteAlertNotification", func(cmd *models.DeleteAlertNotificationWithUidCommand) error {
return sqlStore.DeleteAlertNotificationWithUid(cmd)
})
}
+8 -5
View File
@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
plugifaces "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
"github.com/grafana/grafana/pkg/services/provisioning/datasources"
"github.com/grafana/grafana/pkg/services/provisioning/notifiers"
@@ -17,12 +18,13 @@ import (
"github.com/grafana/grafana/pkg/util/errutil"
)
func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, pluginManager plugifaces.Manager) (
*ProvisioningServiceImpl, error) {
func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, pluginManager plugifaces.Manager,
encryptionService encryption.Service) (*ProvisioningServiceImpl, error) {
s := &ProvisioningServiceImpl{
Cfg: cfg,
SQLStore: sqlStore,
PluginManager: pluginManager,
EncryptionService: encryptionService,
log: log.New("provisioning"),
newDashboardProvisioner: dashboards.New,
provisionNotifiers: notifiers.Provision,
@@ -57,7 +59,7 @@ func NewProvisioningServiceImpl() *ProvisioningServiceImpl {
// Used for testing purposes
func newProvisioningServiceImpl(
newDashboardProvisioner dashboards.DashboardProvisionerFactory,
provisionNotifiers func(string) error,
provisionNotifiers func(string, encryption.Service) error,
provisionDatasources func(string) error,
provisionPlugins func(string, plugifaces.Manager) error,
) *ProvisioningServiceImpl {
@@ -74,11 +76,12 @@ type ProvisioningServiceImpl struct {
Cfg *setting.Cfg
SQLStore *sqlstore.SQLStore
PluginManager plugifaces.Manager
EncryptionService encryption.Service
log log.Logger
pollingCtxCancel context.CancelFunc
newDashboardProvisioner dashboards.DashboardProvisionerFactory
dashboardProvisioner dashboards.DashboardProvisioner
provisionNotifiers func(string) error
provisionNotifiers func(string, encryption.Service) error
provisionDatasources func(string) error
provisionPlugins func(string, plugifaces.Manager) error
mutex sync.Mutex
@@ -146,7 +149,7 @@ func (ps *ProvisioningServiceImpl) ProvisionPlugins() error {
func (ps *ProvisioningServiceImpl) ProvisionNotifications() error {
alertNotificationsPath := filepath.Join(ps.Cfg.ProvisioningPath, "notifiers")
err := ps.provisionNotifiers(alertNotificationsPath)
err := ps.provisionNotifiers(alertNotificationsPath, ps.EncryptionService)
return errutil.Wrap("Alert notification provisioning error", err)
}
+6 -4
View File
@@ -1,6 +1,8 @@
package secrets
import (
"context"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/setting"
)
@@ -17,12 +19,12 @@ func newGrafanaProvider(settings setting.Provider, encryption encryption.Service
}
}
func (p grafanaProvider) Encrypt(blob []byte) ([]byte, error) {
func (p grafanaProvider) Encrypt(ctx context.Context, blob []byte) ([]byte, error) {
key := p.settings.KeyValue("security", "secret_key").Value()
return p.encryption.Encrypt(blob, key)
return p.encryption.Encrypt(ctx, blob, key)
}
func (p grafanaProvider) Decrypt(blob []byte) ([]byte, error) {
func (p grafanaProvider) Decrypt(ctx context.Context, blob []byte) ([]byte, error) {
key := p.settings.KeyValue("security", "secret_key").Value()
return p.encryption.Decrypt(blob, key)
return p.encryption.Decrypt(ctx, blob, key)
}
+6 -6
View File
@@ -55,8 +55,8 @@ type dataKeyCacheItem struct {
}
type Provider interface {
Encrypt(blob []byte) ([]byte, error)
Decrypt(blob []byte) ([]byte, error)
Encrypt(ctx context.Context, blob []byte) ([]byte, error)
Decrypt(ctx context.Context, blob []byte) ([]byte, error)
}
var b64 = base64.RawStdEncoding
@@ -95,7 +95,7 @@ func (s *SecretsService) Encrypt(ctx context.Context, payload []byte, opt Encryp
}
}
encrypted, err := s.enc.Encrypt(payload, string(dataKey))
encrypted, err := s.enc.Encrypt(ctx, payload, string(dataKey))
if err != nil {
return nil, err
}
@@ -142,7 +142,7 @@ func (s *SecretsService) Decrypt(ctx context.Context, payload []byte) ([]byte, e
}
}
return s.enc.Decrypt(payload, string(dataKey))
return s.enc.Decrypt(ctx, payload, string(dataKey))
}
func (s *SecretsService) EncryptJsonData(ctx context.Context, kv map[string]string, opt EncryptionOptions) (map[string][]byte, error) {
@@ -206,7 +206,7 @@ func (s *SecretsService) newDataKey(ctx context.Context, name string, scope stri
}
// 2. Encrypt it
encrypted, err := provider.Encrypt(dataKey)
encrypted, err := provider.Encrypt(ctx, dataKey)
if err != nil {
return nil, err
}
@@ -254,7 +254,7 @@ func (s *SecretsService) dataKey(ctx context.Context, name string) ([]byte, erro
return nil, fmt.Errorf("could not find encryption provider '%s'", dataKey.Provider)
}
decrypted, err := provider.Decrypt(dataKey.EncryptedData)
decrypted, err := provider.Decrypt(ctx, dataKey.EncryptedData)
if err != nil {
return nil, err
}
+1
View File
@@ -108,6 +108,7 @@ func TestSecretsService_DataKeys(t *testing.T) {
Provider: "test",
EncryptedData: []byte{0x62, 0xAF, 0xA1, 0x1A},
}
err := svc.CreateDataKey(ctx, k)
require.Error(t, err)

Some files were not shown because too many files have changed in this diff Show More