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:
Serge Zaitsev 2022-12-06 15:12:27 +01:00 committed by GitHub
parent 2ff6ae65df
commit f1fb202284
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 87 additions and 31 deletions

View File

@ -165,6 +165,9 @@ connstr =
# prefix prepended to all the keys in the remote cache
prefix =
# This enables encryption of values stored in the remote cache
encryption =
#################################### Data proxy ###########################
[dataproxy]

View File

@ -172,6 +172,9 @@
# prefix prepended to all the keys in the remote cache
; prefix =
# This enables encryption of values stored in the remote cache
;encryption =
#################################### Data proxy ###########################
[dataproxy]

View File

@ -14,12 +14,14 @@ const databaseCacheType = "database"
type databaseCache struct {
SQLStore db.DB
codec codec
log log.Logger
}
func newDatabaseCache(sqlstore db.DB) *databaseCache {
func newDatabaseCache(sqlstore db.DB, codec codec) *databaseCache {
dc := &databaseCache{
SQLStore: sqlstore,
codec: codec,
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
}
@ -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 {
item := &cachedItem{Val: value}
data, err := encodeGob(item)
data, err := dc.codec.Encode(ctx, item)
if err != nil {
return err
}

View File

@ -16,6 +16,7 @@ func TestDatabaseStorageGarbageCollection(t *testing.T) {
db := &databaseCache{
SQLStore: sqlstore,
codec: &gobCodec{},
log: log.New("remotecache.database"),
}
@ -64,6 +65,7 @@ func TestSecondSet(t *testing.T) {
db := &databaseCache{
SQLStore: sqlstore,
codec: &gobCodec{},
log: log.New("remotecache.database"),
}

View File

@ -12,11 +12,13 @@ const memcachedCacheType = "memcached"
type memcachedStorage struct {
c *memcache.Client
codec codec
}
func newMemcachedStorage(opts *setting.RemoteCacheOptions) *memcachedStorage {
func newMemcachedStorage(opts *setting.RemoteCacheOptions, codec codec) *memcachedStorage {
return &memcachedStorage{
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.
func (s *memcachedStorage) Set(ctx context.Context, key string, val interface{}, expires time.Duration) error {
item := &cachedItem{Val: val}
bytes, err := encodeGob(item)
bytes, err := s.codec.Encode(ctx, item)
if err != nil {
return err
}
@ -58,7 +60,7 @@ func (s *memcachedStorage) Get(ctx context.Context, key string) (interface{}, er
item := &cachedItem{}
err = decodeGob(memcachedItem.Value, item)
err = s.codec.Decode(ctx, memcachedItem.Value, item)
if err != nil {
return nil, err
}

View File

@ -16,6 +16,7 @@ const redisCacheType = "redis"
type redisStorage struct {
c *redis.Client
codec codec
}
// 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
}
func newRedisStorage(opts *setting.RemoteCacheOptions) (*redisStorage, error) {
func newRedisStorage(opts *setting.RemoteCacheOptions, codec codec) (*redisStorage, error) {
opt, err := parseRedisConnStr(opts.ConnStr)
if err != nil {
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.
func (s *redisStorage) Set(ctx context.Context, key string, val interface{}, expires time.Duration) error {
item := &cachedItem{Val: val}
value, err := encodeGob(item)
value, err := s.codec.Encode(ctx, item)
if err != nil {
return err
}
@ -100,7 +101,7 @@ func (s *redisStorage) Get(ctx context.Context, key string) (interface{}, error)
v := s.c.Get(ctx, key)
item := &cachedItem{}
err := decodeGob([]byte(v.Val()), item)
err := s.codec.Decode(ctx, []byte(v.Val()), item)
if err == nil {
return item.Val, nil

View File

@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
glog "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/setting"
)
@ -29,8 +30,14 @@ const (
ServiceName = "RemoteCache"
)
func ProvideService(cfg *setting.Cfg, sqlStore db.DB) (*RemoteCache, error) {
client, err := createClient(cfg.RemoteCacheOptions, sqlStore)
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, secretsService secrets.Service) (*RemoteCache, error) {
var codec codec
if cfg.RemoteCacheOptions.Encryption {
codec = &encryptionCodec{secretsService}
} else {
codec = &gobCodec{}
}
client, err := createClient(cfg.RemoteCacheOptions, sqlStore, codec)
if err != nil {
return nil, err
}
@ -97,14 +104,14 @@ func (ds *RemoteCache) Run(ctx context.Context) error {
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 {
case redisCacheType:
cache, err = newRedisStorage(opts)
cache, err = newRedisStorage(opts, codec)
case memcachedCacheType:
cache = newMemcachedStorage(opts)
cache = newMemcachedStorage(opts, codec)
case databaseCacheType:
cache = newDatabaseCache(sqlstore)
cache = newDatabaseCache(sqlstore, codec)
default:
return nil, ErrInvalidCacheType
}
@ -131,17 +138,46 @@ type cachedItem struct {
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)
err := gob.NewEncoder(buf).Encode(item)
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)
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 {
cache CacheStorage
prefix string

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
"github.com/grafana/grafana/pkg/setting"
)
@ -28,7 +29,7 @@ func createTestClient(t *testing.T, opts *setting.RemoteCacheOptions, sqlstore d
cfg := &setting.Cfg{
RemoteCacheOptions: opts,
}
dc, err := ProvideService(cfg, sqlstore)
dc, err := ProvideService(cfg, sqlstore, fakes.NewFakeSecretsService())
require.Nil(t, err, "Failed to init client for test")
return dc
@ -46,7 +47,7 @@ func TestCachedBasedOnConfig(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)
}
@ -95,6 +96,7 @@ func TestCachePrefix(t *testing.T) {
cache := &databaseCache{
SQLStore: db,
log: log.New("remotecache.database"),
codec: &gobCodec{},
}
prefixCache := &prefixCacheStorage{cache: cache, prefix: "test/"}

View File

@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
"github.com/grafana/grafana/pkg/setting"
)
@ -22,7 +23,7 @@ func NewFakeStore(t *testing.T) *RemoteCache {
dc, err := ProvideService(&setting.Cfg{
RemoteCacheOptions: opts,
}, sqlStore)
}, sqlStore, fakes.NewFakeSecretsService())
require.NoError(t, err, "Failed to init remote cache for test")
return dc

View File

@ -19,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/services/login/loginservice"
"github.com/grafana/grafana/pkg/services/org/orgtest"
"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/usertest"
"github.com/grafana/grafana/pkg/setting"
@ -79,7 +80,7 @@ func getContextHandler(t *testing.T) *ContextHandler {
cfg.AuthProxyHeaderName = "X-Killa"
cfg.AuthProxyEnabled = true
cfg.AuthProxyHeaderProperty = "username"
remoteCacheSvc, err := remotecache.ProvideService(cfg, sqlStore)
remoteCacheSvc, err := remotecache.ProvideService(cfg, sqlStore, fakes.NewFakeSecretsService())
require.NoError(t, err)
userAuthTokenSvc := authtest.NewFakeUserAuthTokenService()
renderSvc := &fakeRenderService{}

View File

@ -1121,11 +1121,13 @@ func (cfg *Cfg) Load(args CommandLineArgs) error {
dbName := valueAsString(cacheServer, "type", "database")
connStr := valueAsString(cacheServer, "connstr", "")
prefix := valueAsString(cacheServer, "prefix", "")
encryption := cacheServer.Key("encryption").MustBool(false)
cfg.RemoteCacheOptions = &RemoteCacheOptions{
Name: dbName,
ConnStr: connStr,
Prefix: prefix,
Encryption: encryption,
}
geomapSection := iniFile.Section("geomap")
@ -1162,6 +1164,7 @@ type RemoteCacheOptions struct {
Name string
ConnStr string
Prefix string
Encryption bool
}
func (cfg *Cfg) readLDAPConfig() {