grafana/pkg/services/secrets/manager/manager.go
Tania B 5652bde447
Encryption: Use secrets service (#40251)
* Use secrets service in pluginproxy

* Use secrets service in pluginxontext

* Use secrets service in pluginsettings

* Use secrets service in provisioning

* Use secrets service in authinfoservice

* Use secrets service in api

* Use secrets service in sqlstore

* Use secrets service in dashboardshapshots

* Use secrets service in tsdb

* Use secrets service in datasources

* Use secrets service in alerting

* Use secrets service in ngalert

* Break cyclic dependancy

* Refactor service

* Break cyclic dependancy

* Add FakeSecretsStore

* Setup Secrets Service in sqlstore

* Fix

* Continue secrets service refactoring

* Fix cyclic dependancy in sqlstore tests

* Fix secrets service references

* Fix linter errors

* Add fake secrets service for tests

* Refactor SetupTestSecretsService

* Update setting up secret service in tests

* Fix missing secrets service in multiorg_alertmanager_test

* Use fake db in tests and sort imports

* Use fake db in datasources tests

* Fix more tests

* Fix linter issues

* Attempt to fix plugin proxy tests

* Pass secrets service to getPluginProxiedRequest in pluginproxy tests

* Fix pluginproxy tests

* Revert using secrets service in alerting and provisioning

* Update decryptFn in alerting migration

* Rename defaultProvider to currentProvider

* Use fake secrets service in alert channels tests

* Refactor secrets service test helper

* Update setting up secrets service in tests

* Revert alerting changes in api

* Add comments

* Remove secrets service from background services

* Convert global encryption functions into vars

* Revert "Convert global encryption functions into vars"

This reverts commit 498eb19859.

* Add feature toggle for envelope encryption

* Rename toggle

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
Co-authored-by: Joan López de la Franca Beltran <joanjan14@gmail.com>
2021-11-04 18:47:21 +02:00

260 lines
6.6 KiB
Go

package manager
import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/secrets"
grafana "github.com/grafana/grafana/pkg/services/secrets/defaultprovider"
"github.com/grafana/grafana/pkg/setting"
)
const (
defaultProvider = "secretKey"
envelopeEncryptionFeatureToggle = "envelopeEncryption"
)
type SecretsService struct {
store secrets.Store
bus bus.Bus
enc encryption.Service
settings setting.Provider
currentProvider string
providers map[string]secrets.Provider
dataKeyCache map[string]dataKeyCacheItem
}
func ProvideSecretsService(store secrets.Store, bus bus.Bus, enc encryption.Service, settings setting.Provider) *SecretsService {
providers := map[string]secrets.Provider{
defaultProvider: grafana.New(settings, enc),
}
s := &SecretsService{
store: store,
bus: bus,
enc: enc,
settings: settings,
currentProvider: defaultProvider,
providers: providers,
dataKeyCache: make(map[string]dataKeyCacheItem),
}
return s
}
type dataKeyCacheItem struct {
expiry time.Time
dataKey []byte
}
var b64 = base64.RawStdEncoding
func (s *SecretsService) Encrypt(ctx context.Context, payload []byte, opt secrets.EncryptionOptions) ([]byte, error) {
// Use legacy encryption service if envelopeEncryptionFeatureToggle toggle is off
if !s.settings.IsFeatureToggleEnabled(envelopeEncryptionFeatureToggle) {
return s.enc.Encrypt(ctx, payload, setting.SecretKey)
}
// If encryption envelopeEncryptionFeatureToggle toggle is on, use envelope encryption
scope := opt()
keyName := fmt.Sprintf("%s/%s@%s", time.Now().Format("2006-01-02"), scope, s.currentProvider)
dataKey, err := s.dataKey(ctx, keyName)
if err != nil {
if errors.Is(err, secrets.ErrDataKeyNotFound) {
dataKey, err = s.newDataKey(ctx, keyName, scope)
if err != nil {
return nil, err
}
} else {
return nil, err
}
}
encrypted, err := s.enc.Encrypt(ctx, payload, string(dataKey))
if err != nil {
return nil, err
}
prefix := make([]byte, b64.EncodedLen(len(keyName))+2)
b64.Encode(prefix[1:], []byte(keyName))
prefix[0] = '#'
prefix[len(prefix)-1] = '#'
blob := make([]byte, len(prefix)+len(encrypted))
copy(blob, prefix)
copy(blob[len(prefix):], encrypted)
return blob, nil
}
func (s *SecretsService) Decrypt(ctx context.Context, payload []byte) ([]byte, error) {
// Use legacy encryption service if envelopeEncryptionFeatureToggle toggle is off
if !s.settings.IsFeatureToggleEnabled(envelopeEncryptionFeatureToggle) {
return s.enc.Decrypt(ctx, payload, setting.SecretKey)
}
// If encryption envelopeEncryptionFeatureToggle toggle is on, use envelope encryption
if len(payload) == 0 {
return nil, fmt.Errorf("unable to decrypt empty payload")
}
var dataKey []byte
if payload[0] != '#' {
secretKey := s.settings.KeyValue("security", "secret_key").Value()
dataKey = []byte(secretKey)
} else {
payload = payload[1:]
endOfKey := bytes.Index(payload, []byte{'#'})
if endOfKey == -1 {
return nil, fmt.Errorf("could not find valid key in encrypted payload")
}
b64Key := payload[:endOfKey]
payload = payload[endOfKey+1:]
key := make([]byte, b64.DecodedLen(len(b64Key)))
_, err := b64.Decode(key, b64Key)
if err != nil {
return nil, err
}
dataKey, err = s.dataKey(ctx, string(key))
if err != nil {
return nil, err
}
}
return s.enc.Decrypt(ctx, payload, string(dataKey))
}
func (s *SecretsService) EncryptJsonData(ctx context.Context, kv map[string]string, opt secrets.EncryptionOptions) (map[string][]byte, error) {
encrypted := make(map[string][]byte)
for key, value := range kv {
encryptedData, err := s.Encrypt(ctx, []byte(value), opt)
if err != nil {
return nil, err
}
encrypted[key] = encryptedData
}
return encrypted, nil
}
func (s *SecretsService) DecryptJsonData(ctx context.Context, sjd map[string][]byte) (map[string]string, error) {
decrypted := make(map[string]string)
for key, data := range sjd {
decryptedData, err := s.Decrypt(ctx, data)
if err != nil {
return nil, err
}
decrypted[key] = string(decryptedData)
}
return decrypted, nil
}
func (s *SecretsService) GetDecryptedValue(ctx context.Context, sjd map[string][]byte, key, fallback string) string {
if value, ok := sjd[key]; ok {
decryptedData, err := s.Decrypt(ctx, value)
if err != nil {
return fallback
}
return string(decryptedData)
}
return fallback
}
func newRandomDataKey() ([]byte, error) {
rawDataKey := make([]byte, 16)
_, err := rand.Read(rawDataKey)
if err != nil {
return nil, err
}
return rawDataKey, nil
}
// newDataKey creates a new random DEK, caches it and returns its value
func (s *SecretsService) newDataKey(ctx context.Context, name string, scope string) ([]byte, error) {
// 1. Create new DEK
dataKey, err := newRandomDataKey()
if err != nil {
return nil, err
}
provider, exists := s.providers[s.currentProvider]
if !exists {
return nil, fmt.Errorf("could not find encryption provider '%s'", s.currentProvider)
}
// 2. Encrypt it
encrypted, err := provider.Encrypt(ctx, dataKey)
if err != nil {
return nil, err
}
// 3. Store its encrypted value in db
err = s.store.CreateDataKey(ctx, secrets.DataKey{
Active: true, // TODO: right now we never mark a key as deactivated
Name: name,
Provider: s.currentProvider,
EncryptedData: encrypted,
Scope: scope,
})
if err != nil {
return nil, err
}
// 4. Cache its unencrypted value and return it
s.dataKeyCache[name] = dataKeyCacheItem{
expiry: time.Now().Add(15 * time.Minute),
dataKey: dataKey,
}
return dataKey, nil
}
// dataKey looks up DEK in cache or database, and decrypts it
func (s *SecretsService) dataKey(ctx context.Context, name string) ([]byte, error) {
if item, exists := s.dataKeyCache[name]; exists {
if item.expiry.Before(time.Now()) && !item.expiry.IsZero() {
delete(s.dataKeyCache, name)
} else {
return item.dataKey, nil
}
}
// 1. get encrypted data key from database
dataKey, err := s.store.GetDataKey(ctx, name)
if err != nil {
return nil, err
}
// 2. decrypt data key
provider, exists := s.providers[dataKey.Provider]
if !exists {
return nil, fmt.Errorf("could not find encryption provider '%s'", dataKey.Provider)
}
decrypted, err := provider.Decrypt(ctx, dataKey.EncryptedData)
if err != nil {
return nil, err
}
// 3. cache data key
s.dataKeyCache[name] = dataKeyCacheItem{
expiry: time.Now().Add(15 * time.Minute),
dataKey: decrypted,
}
return decrypted, nil
}