mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Add encryption codec to the remote cache (#59871)
* add encryption codec to the remote cache * change config files too * fix test constructor * pass codec into the test cache
This commit is contained in:
parent
2ff6ae65df
commit
f1fb202284
@ -165,6 +165,9 @@ connstr =
|
|||||||
# prefix prepended to all the keys in the remote cache
|
# prefix prepended to all the keys in the remote cache
|
||||||
prefix =
|
prefix =
|
||||||
|
|
||||||
|
# This enables encryption of values stored in the remote cache
|
||||||
|
encryption =
|
||||||
|
|
||||||
#################################### Data proxy ###########################
|
#################################### Data proxy ###########################
|
||||||
[dataproxy]
|
[dataproxy]
|
||||||
|
|
||||||
|
@ -172,6 +172,9 @@
|
|||||||
# prefix prepended to all the keys in the remote cache
|
# prefix prepended to all the keys in the remote cache
|
||||||
; prefix =
|
; prefix =
|
||||||
|
|
||||||
|
# This enables encryption of values stored in the remote cache
|
||||||
|
;encryption =
|
||||||
|
|
||||||
#################################### Data proxy ###########################
|
#################################### Data proxy ###########################
|
||||||
[dataproxy]
|
[dataproxy]
|
||||||
|
|
||||||
|
@ -14,12 +14,14 @@ const databaseCacheType = "database"
|
|||||||
|
|
||||||
type databaseCache struct {
|
type databaseCache struct {
|
||||||
SQLStore db.DB
|
SQLStore db.DB
|
||||||
|
codec codec
|
||||||
log log.Logger
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDatabaseCache(sqlstore db.DB) *databaseCache {
|
func newDatabaseCache(sqlstore db.DB, codec codec) *databaseCache {
|
||||||
dc := &databaseCache{
|
dc := &databaseCache{
|
||||||
SQLStore: sqlstore,
|
SQLStore: sqlstore,
|
||||||
|
codec: codec,
|
||||||
log: log.New("remotecache.database"),
|
log: log.New("remotecache.database"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +80,7 @@ func (dc *databaseCache) Get(ctx context.Context, key string) (interface{}, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = decodeGob(cacheHit.Data, item); err != nil {
|
if err = dc.codec.Decode(ctx, cacheHit.Data, item); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +92,7 @@ func (dc *databaseCache) Get(ctx context.Context, key string) (interface{}, erro
|
|||||||
|
|
||||||
func (dc *databaseCache) Set(ctx context.Context, key string, value interface{}, expire time.Duration) error {
|
func (dc *databaseCache) Set(ctx context.Context, key string, value interface{}, expire time.Duration) error {
|
||||||
item := &cachedItem{Val: value}
|
item := &cachedItem{Val: value}
|
||||||
data, err := encodeGob(item)
|
data, err := dc.codec.Encode(ctx, item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ func TestDatabaseStorageGarbageCollection(t *testing.T) {
|
|||||||
|
|
||||||
db := &databaseCache{
|
db := &databaseCache{
|
||||||
SQLStore: sqlstore,
|
SQLStore: sqlstore,
|
||||||
|
codec: &gobCodec{},
|
||||||
log: log.New("remotecache.database"),
|
log: log.New("remotecache.database"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ func TestSecondSet(t *testing.T) {
|
|||||||
|
|
||||||
db := &databaseCache{
|
db := &databaseCache{
|
||||||
SQLStore: sqlstore,
|
SQLStore: sqlstore,
|
||||||
|
codec: &gobCodec{},
|
||||||
log: log.New("remotecache.database"),
|
log: log.New("remotecache.database"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,12 +11,14 @@ import (
|
|||||||
const memcachedCacheType = "memcached"
|
const memcachedCacheType = "memcached"
|
||||||
|
|
||||||
type memcachedStorage struct {
|
type memcachedStorage struct {
|
||||||
c *memcache.Client
|
c *memcache.Client
|
||||||
|
codec codec
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMemcachedStorage(opts *setting.RemoteCacheOptions) *memcachedStorage {
|
func newMemcachedStorage(opts *setting.RemoteCacheOptions, codec codec) *memcachedStorage {
|
||||||
return &memcachedStorage{
|
return &memcachedStorage{
|
||||||
c: memcache.New(opts.ConnStr),
|
c: memcache.New(opts.ConnStr),
|
||||||
|
codec: codec,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +33,7 @@ func newItem(sid string, data []byte, expire int32) *memcache.Item {
|
|||||||
// Set sets value to given key in the cache.
|
// Set sets value to given key in the cache.
|
||||||
func (s *memcachedStorage) Set(ctx context.Context, key string, val interface{}, expires time.Duration) error {
|
func (s *memcachedStorage) Set(ctx context.Context, key string, val interface{}, expires time.Duration) error {
|
||||||
item := &cachedItem{Val: val}
|
item := &cachedItem{Val: val}
|
||||||
bytes, err := encodeGob(item)
|
bytes, err := s.codec.Encode(ctx, item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -58,7 +60,7 @@ func (s *memcachedStorage) Get(ctx context.Context, key string) (interface{}, er
|
|||||||
|
|
||||||
item := &cachedItem{}
|
item := &cachedItem{}
|
||||||
|
|
||||||
err = decodeGob(memcachedItem.Value, item)
|
err = s.codec.Decode(ctx, memcachedItem.Value, item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,8 @@ import (
|
|||||||
const redisCacheType = "redis"
|
const redisCacheType = "redis"
|
||||||
|
|
||||||
type redisStorage struct {
|
type redisStorage struct {
|
||||||
c *redis.Client
|
c *redis.Client
|
||||||
|
codec codec
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseRedisConnStr parses k=v pairs in csv and builds a redis Options object
|
// parseRedisConnStr parses k=v pairs in csv and builds a redis Options object
|
||||||
@ -76,18 +77,18 @@ func parseRedisConnStr(connStr string) (*redis.Options, error) {
|
|||||||
return options, nil
|
return options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRedisStorage(opts *setting.RemoteCacheOptions) (*redisStorage, error) {
|
func newRedisStorage(opts *setting.RemoteCacheOptions, codec codec) (*redisStorage, error) {
|
||||||
opt, err := parseRedisConnStr(opts.ConnStr)
|
opt, err := parseRedisConnStr(opts.ConnStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &redisStorage{c: redis.NewClient(opt)}, nil
|
return &redisStorage{c: redis.NewClient(opt), codec: codec}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets value to given key in session.
|
// Set sets value to given key in session.
|
||||||
func (s *redisStorage) Set(ctx context.Context, key string, val interface{}, expires time.Duration) error {
|
func (s *redisStorage) Set(ctx context.Context, key string, val interface{}, expires time.Duration) error {
|
||||||
item := &cachedItem{Val: val}
|
item := &cachedItem{Val: val}
|
||||||
value, err := encodeGob(item)
|
value, err := s.codec.Encode(ctx, item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -100,7 +101,7 @@ func (s *redisStorage) Get(ctx context.Context, key string) (interface{}, error)
|
|||||||
v := s.c.Get(ctx, key)
|
v := s.c.Get(ctx, key)
|
||||||
|
|
||||||
item := &cachedItem{}
|
item := &cachedItem{}
|
||||||
err := decodeGob([]byte(v.Val()), item)
|
err := s.codec.Decode(ctx, []byte(v.Val()), item)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return item.Val, nil
|
return item.Val, nil
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
glog "github.com/grafana/grafana/pkg/infra/log"
|
glog "github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
|
"github.com/grafana/grafana/pkg/services/secrets"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,8 +30,14 @@ const (
|
|||||||
ServiceName = "RemoteCache"
|
ServiceName = "RemoteCache"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProvideService(cfg *setting.Cfg, sqlStore db.DB) (*RemoteCache, error) {
|
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, secretsService secrets.Service) (*RemoteCache, error) {
|
||||||
client, err := createClient(cfg.RemoteCacheOptions, sqlStore)
|
var codec codec
|
||||||
|
if cfg.RemoteCacheOptions.Encryption {
|
||||||
|
codec = &encryptionCodec{secretsService}
|
||||||
|
} else {
|
||||||
|
codec = &gobCodec{}
|
||||||
|
}
|
||||||
|
client, err := createClient(cfg.RemoteCacheOptions, sqlStore, codec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -97,14 +104,14 @@ func (ds *RemoteCache) Run(ctx context.Context) error {
|
|||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func createClient(opts *setting.RemoteCacheOptions, sqlstore db.DB) (cache CacheStorage, err error) {
|
func createClient(opts *setting.RemoteCacheOptions, sqlstore db.DB, codec codec) (cache CacheStorage, err error) {
|
||||||
switch opts.Name {
|
switch opts.Name {
|
||||||
case redisCacheType:
|
case redisCacheType:
|
||||||
cache, err = newRedisStorage(opts)
|
cache, err = newRedisStorage(opts, codec)
|
||||||
case memcachedCacheType:
|
case memcachedCacheType:
|
||||||
cache = newMemcachedStorage(opts)
|
cache = newMemcachedStorage(opts, codec)
|
||||||
case databaseCacheType:
|
case databaseCacheType:
|
||||||
cache = newDatabaseCache(sqlstore)
|
cache = newDatabaseCache(sqlstore, codec)
|
||||||
default:
|
default:
|
||||||
return nil, ErrInvalidCacheType
|
return nil, ErrInvalidCacheType
|
||||||
}
|
}
|
||||||
@ -131,17 +138,46 @@ type cachedItem struct {
|
|||||||
Val interface{}
|
Val interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeGob(item *cachedItem) ([]byte, error) {
|
type codec interface {
|
||||||
|
Encode(context.Context, *cachedItem) ([]byte, error)
|
||||||
|
Decode(context.Context, []byte, *cachedItem) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type gobCodec struct{}
|
||||||
|
|
||||||
|
func (c *gobCodec) Encode(_ context.Context, item *cachedItem) ([]byte, error) {
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
err := gob.NewEncoder(buf).Encode(item)
|
err := gob.NewEncoder(buf).Encode(item)
|
||||||
return buf.Bytes(), err
|
return buf.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeGob(data []byte, out *cachedItem) error {
|
func (c *gobCodec) Decode(_ context.Context, data []byte, out *cachedItem) error {
|
||||||
buf := bytes.NewBuffer(data)
|
buf := bytes.NewBuffer(data)
|
||||||
return gob.NewDecoder(buf).Decode(&out)
|
return gob.NewDecoder(buf).Decode(&out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type encryptionCodec struct {
|
||||||
|
secretsService secrets.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *encryptionCodec) Encode(ctx context.Context, item *cachedItem) ([]byte, error) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
err := gob.NewEncoder(buf).Encode(item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.secretsService.Encrypt(ctx, buf.Bytes(), secrets.WithoutScope())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *encryptionCodec) Decode(ctx context.Context, data []byte, out *cachedItem) error {
|
||||||
|
decrypted, err := c.secretsService.Decrypt(ctx, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf := bytes.NewBuffer(decrypted)
|
||||||
|
return gob.NewDecoder(buf).Decode(&out)
|
||||||
|
}
|
||||||
|
|
||||||
type prefixCacheStorage struct {
|
type prefixCacheStorage struct {
|
||||||
cache CacheStorage
|
cache CacheStorage
|
||||||
prefix string
|
prefix string
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ func createTestClient(t *testing.T, opts *setting.RemoteCacheOptions, sqlstore d
|
|||||||
cfg := &setting.Cfg{
|
cfg := &setting.Cfg{
|
||||||
RemoteCacheOptions: opts,
|
RemoteCacheOptions: opts,
|
||||||
}
|
}
|
||||||
dc, err := ProvideService(cfg, sqlstore)
|
dc, err := ProvideService(cfg, sqlstore, fakes.NewFakeSecretsService())
|
||||||
require.Nil(t, err, "Failed to init client for test")
|
require.Nil(t, err, "Failed to init client for test")
|
||||||
|
|
||||||
return dc
|
return dc
|
||||||
@ -46,7 +47,7 @@ func TestCachedBasedOnConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidCacheTypeReturnsError(t *testing.T) {
|
func TestInvalidCacheTypeReturnsError(t *testing.T) {
|
||||||
_, err := createClient(&setting.RemoteCacheOptions{Name: "invalid"}, nil)
|
_, err := createClient(&setting.RemoteCacheOptions{Name: "invalid"}, nil, &gobCodec{})
|
||||||
assert.Equal(t, err, ErrInvalidCacheType)
|
assert.Equal(t, err, ErrInvalidCacheType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +96,7 @@ func TestCachePrefix(t *testing.T) {
|
|||||||
cache := &databaseCache{
|
cache := &databaseCache{
|
||||||
SQLStore: db,
|
SQLStore: db,
|
||||||
log: log.New("remotecache.database"),
|
log: log.New("remotecache.database"),
|
||||||
|
codec: &gobCodec{},
|
||||||
}
|
}
|
||||||
prefixCache := &prefixCacheStorage{cache: cache, prefix: "test/"}
|
prefixCache := &prefixCacheStorage{cache: cache, prefix: "test/"}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
|
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ func NewFakeStore(t *testing.T) *RemoteCache {
|
|||||||
|
|
||||||
dc, err := ProvideService(&setting.Cfg{
|
dc, err := ProvideService(&setting.Cfg{
|
||||||
RemoteCacheOptions: opts,
|
RemoteCacheOptions: opts,
|
||||||
}, sqlStore)
|
}, sqlStore, fakes.NewFakeSecretsService())
|
||||||
require.NoError(t, err, "Failed to init remote cache for test")
|
require.NoError(t, err, "Failed to init remote cache for test")
|
||||||
|
|
||||||
return dc
|
return dc
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||||
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
||||||
"github.com/grafana/grafana/pkg/services/rendering"
|
"github.com/grafana/grafana/pkg/services/rendering"
|
||||||
|
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
"github.com/grafana/grafana/pkg/services/user/usertest"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
@ -79,7 +80,7 @@ func getContextHandler(t *testing.T) *ContextHandler {
|
|||||||
cfg.AuthProxyHeaderName = "X-Killa"
|
cfg.AuthProxyHeaderName = "X-Killa"
|
||||||
cfg.AuthProxyEnabled = true
|
cfg.AuthProxyEnabled = true
|
||||||
cfg.AuthProxyHeaderProperty = "username"
|
cfg.AuthProxyHeaderProperty = "username"
|
||||||
remoteCacheSvc, err := remotecache.ProvideService(cfg, sqlStore)
|
remoteCacheSvc, err := remotecache.ProvideService(cfg, sqlStore, fakes.NewFakeSecretsService())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
userAuthTokenSvc := authtest.NewFakeUserAuthTokenService()
|
userAuthTokenSvc := authtest.NewFakeUserAuthTokenService()
|
||||||
renderSvc := &fakeRenderService{}
|
renderSvc := &fakeRenderService{}
|
||||||
|
@ -1121,11 +1121,13 @@ func (cfg *Cfg) Load(args CommandLineArgs) error {
|
|||||||
dbName := valueAsString(cacheServer, "type", "database")
|
dbName := valueAsString(cacheServer, "type", "database")
|
||||||
connStr := valueAsString(cacheServer, "connstr", "")
|
connStr := valueAsString(cacheServer, "connstr", "")
|
||||||
prefix := valueAsString(cacheServer, "prefix", "")
|
prefix := valueAsString(cacheServer, "prefix", "")
|
||||||
|
encryption := cacheServer.Key("encryption").MustBool(false)
|
||||||
|
|
||||||
cfg.RemoteCacheOptions = &RemoteCacheOptions{
|
cfg.RemoteCacheOptions = &RemoteCacheOptions{
|
||||||
Name: dbName,
|
Name: dbName,
|
||||||
ConnStr: connStr,
|
ConnStr: connStr,
|
||||||
Prefix: prefix,
|
Prefix: prefix,
|
||||||
|
Encryption: encryption,
|
||||||
}
|
}
|
||||||
|
|
||||||
geomapSection := iniFile.Section("geomap")
|
geomapSection := iniFile.Section("geomap")
|
||||||
@ -1159,9 +1161,10 @@ func valueAsString(section *ini.Section, keyName string, defaultValue string) st
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RemoteCacheOptions struct {
|
type RemoteCacheOptions struct {
|
||||||
Name string
|
Name string
|
||||||
ConnStr string
|
ConnStr string
|
||||||
Prefix string
|
Prefix string
|
||||||
|
Encryption bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Cfg) readLDAPConfig() {
|
func (cfg *Cfg) readLDAPConfig() {
|
||||||
|
Loading…
Reference in New Issue
Block a user