mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Encryption: Improve the DX of encryption operations within database transactions (#41654)
* Move user oauth info encryption away from db transaction * Add encryption methods with support for db session reusability
This commit is contained in:
parent
86d79e4625
commit
d3e19b1b3b
@ -80,72 +80,73 @@ func (s *Implementation) GetAuthInfo(query *models.GetAuthInfoQuery) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Implementation) SetAuthInfo(cmd *models.SetAuthInfoCommand) error {
|
func (s *Implementation) SetAuthInfo(cmd *models.SetAuthInfoCommand) error {
|
||||||
|
authUser := &models.UserAuth{
|
||||||
|
UserId: cmd.UserId,
|
||||||
|
AuthModule: cmd.AuthModule,
|
||||||
|
AuthId: cmd.AuthId,
|
||||||
|
Created: getTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.OAuthToken != nil {
|
||||||
|
secretAccessToken, err := s.encryptAndEncode(cmd.OAuthToken.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
secretRefreshToken, err := s.encryptAndEncode(cmd.OAuthToken.RefreshToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
secretTokenType, err := s.encryptAndEncode(cmd.OAuthToken.TokenType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authUser.OAuthAccessToken = secretAccessToken
|
||||||
|
authUser.OAuthRefreshToken = secretRefreshToken
|
||||||
|
authUser.OAuthTokenType = secretTokenType
|
||||||
|
authUser.OAuthExpiry = cmd.OAuthToken.Expiry
|
||||||
|
}
|
||||||
|
|
||||||
return s.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return s.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
authUser := &models.UserAuth{
|
|
||||||
UserId: cmd.UserId,
|
|
||||||
AuthModule: cmd.AuthModule,
|
|
||||||
AuthId: cmd.AuthId,
|
|
||||||
Created: getTime(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.OAuthToken != nil {
|
|
||||||
secretAccessToken, err := s.encryptAndEncode(cmd.OAuthToken.AccessToken)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
secretRefreshToken, err := s.encryptAndEncode(cmd.OAuthToken.RefreshToken)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
secretTokenType, err := s.encryptAndEncode(cmd.OAuthToken.TokenType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
authUser.OAuthAccessToken = secretAccessToken
|
|
||||||
authUser.OAuthRefreshToken = secretRefreshToken
|
|
||||||
authUser.OAuthTokenType = secretTokenType
|
|
||||||
authUser.OAuthExpiry = cmd.OAuthToken.Expiry
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := sess.Insert(authUser)
|
_, err := sess.Insert(authUser)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Implementation) UpdateAuthInfo(cmd *models.UpdateAuthInfoCommand) error {
|
func (s *Implementation) UpdateAuthInfo(cmd *models.UpdateAuthInfoCommand) error {
|
||||||
|
authUser := &models.UserAuth{
|
||||||
|
UserId: cmd.UserId,
|
||||||
|
AuthModule: cmd.AuthModule,
|
||||||
|
AuthId: cmd.AuthId,
|
||||||
|
Created: getTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.OAuthToken != nil {
|
||||||
|
secretAccessToken, err := s.encryptAndEncode(cmd.OAuthToken.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
secretRefreshToken, err := s.encryptAndEncode(cmd.OAuthToken.RefreshToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
secretTokenType, err := s.encryptAndEncode(cmd.OAuthToken.TokenType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
authUser.OAuthAccessToken = secretAccessToken
|
||||||
|
authUser.OAuthRefreshToken = secretRefreshToken
|
||||||
|
authUser.OAuthTokenType = secretTokenType
|
||||||
|
authUser.OAuthExpiry = cmd.OAuthToken.Expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
cond := &models.UserAuth{
|
||||||
|
UserId: cmd.UserId,
|
||||||
|
AuthModule: cmd.AuthModule,
|
||||||
|
}
|
||||||
|
|
||||||
return s.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return s.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
authUser := &models.UserAuth{
|
|
||||||
UserId: cmd.UserId,
|
|
||||||
AuthModule: cmd.AuthModule,
|
|
||||||
AuthId: cmd.AuthId,
|
|
||||||
Created: getTime(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.OAuthToken != nil {
|
|
||||||
secretAccessToken, err := s.encryptAndEncode(cmd.OAuthToken.AccessToken)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
secretRefreshToken, err := s.encryptAndEncode(cmd.OAuthToken.RefreshToken)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
secretTokenType, err := s.encryptAndEncode(cmd.OAuthToken.TokenType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
authUser.OAuthAccessToken = secretAccessToken
|
|
||||||
authUser.OAuthRefreshToken = secretRefreshToken
|
|
||||||
authUser.OAuthTokenType = secretTokenType
|
|
||||||
authUser.OAuthExpiry = cmd.OAuthToken.Expiry
|
|
||||||
}
|
|
||||||
|
|
||||||
cond := &models.UserAuth{
|
|
||||||
UserId: cmd.UserId,
|
|
||||||
AuthModule: cmd.AuthModule,
|
|
||||||
}
|
|
||||||
upd, err := sess.Update(authUser, cond)
|
upd, err := sess.Update(authUser, cond)
|
||||||
s.logger.Debug("Updated user_auth", "user_id", cmd.UserId, "auth_module", cmd.AuthModule, "rows", upd)
|
s.logger.Debug("Updated user_auth", "user_id", cmd.UserId, "auth_module", cmd.AuthModule, "rows", upd)
|
||||||
return err
|
return err
|
||||||
|
@ -5,12 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/secrets"
|
|
||||||
|
|
||||||
"xorm.io/xorm"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/services/secrets"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dataKeysTable = "data_keys"
|
const dataKeysTable = "data_keys"
|
||||||
|
@ -36,7 +36,7 @@ func (f FakeSecretsStore) CreateDataKey(_ context.Context, dataKey secrets.DataK
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f FakeSecretsStore) CreateDataKeyWithDBSession(ctx context.Context, dataKey secrets.DataKey, sess *xorm.Session) error {
|
func (f FakeSecretsStore) CreateDataKeyWithDBSession(_ context.Context, dataKey secrets.DataKey, _ *xorm.Session) error {
|
||||||
f.store[dataKey.Name] = &dataKey
|
f.store[dataKey.Name] = &dataKey
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/secrets"
|
"github.com/grafana/grafana/pkg/services/secrets"
|
||||||
grafana "github.com/grafana/grafana/pkg/services/secrets/defaultprovider"
|
grafana "github.com/grafana/grafana/pkg/services/secrets/defaultprovider"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -59,6 +60,10 @@ type dataKeyCacheItem struct {
|
|||||||
var b64 = base64.RawStdEncoding
|
var b64 = base64.RawStdEncoding
|
||||||
|
|
||||||
func (s *SecretsService) Encrypt(ctx context.Context, payload []byte, opt secrets.EncryptionOptions) ([]byte, error) {
|
func (s *SecretsService) Encrypt(ctx context.Context, payload []byte, opt secrets.EncryptionOptions) ([]byte, error) {
|
||||||
|
return s.EncryptWithDBSession(ctx, payload, opt, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SecretsService) EncryptWithDBSession(ctx context.Context, payload []byte, opt secrets.EncryptionOptions, sess *xorm.Session) ([]byte, error) {
|
||||||
// Use legacy encryption service if envelopeEncryptionFeatureToggle toggle is off
|
// Use legacy encryption service if envelopeEncryptionFeatureToggle toggle is off
|
||||||
if !s.settings.IsFeatureToggleEnabled(envelopeEncryptionFeatureToggle) {
|
if !s.settings.IsFeatureToggleEnabled(envelopeEncryptionFeatureToggle) {
|
||||||
return s.enc.Encrypt(ctx, payload, setting.SecretKey)
|
return s.enc.Encrypt(ctx, payload, setting.SecretKey)
|
||||||
@ -71,7 +76,7 @@ func (s *SecretsService) Encrypt(ctx context.Context, payload []byte, opt secret
|
|||||||
dataKey, err := s.dataKey(ctx, keyName)
|
dataKey, err := s.dataKey(ctx, keyName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, secrets.ErrDataKeyNotFound) {
|
if errors.Is(err, secrets.ErrDataKeyNotFound) {
|
||||||
dataKey, err = s.newDataKey(ctx, keyName, scope)
|
dataKey, err = s.newDataKey(ctx, keyName, scope, sess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -137,9 +142,13 @@ func (s *SecretsService) Decrypt(ctx context.Context, payload []byte) ([]byte, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SecretsService) EncryptJsonData(ctx context.Context, kv map[string]string, opt secrets.EncryptionOptions) (map[string][]byte, error) {
|
func (s *SecretsService) EncryptJsonData(ctx context.Context, kv map[string]string, opt secrets.EncryptionOptions) (map[string][]byte, error) {
|
||||||
|
return s.EncryptJsonDataWithDBSession(ctx, kv, opt, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SecretsService) EncryptJsonDataWithDBSession(ctx context.Context, kv map[string]string, opt secrets.EncryptionOptions, sess *xorm.Session) (map[string][]byte, error) {
|
||||||
encrypted := make(map[string][]byte)
|
encrypted := make(map[string][]byte)
|
||||||
for key, value := range kv {
|
for key, value := range kv {
|
||||||
encryptedData, err := s.Encrypt(ctx, []byte(value), opt)
|
encryptedData, err := s.EncryptWithDBSession(ctx, []byte(value), opt, sess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -185,7 +194,7 @@ func newRandomDataKey() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newDataKey creates a new random DEK, caches it and returns its value
|
// 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) {
|
func (s *SecretsService) newDataKey(ctx context.Context, name string, scope string, sess *xorm.Session) ([]byte, error) {
|
||||||
// 1. Create new DEK
|
// 1. Create new DEK
|
||||||
dataKey, err := newRandomDataKey()
|
dataKey, err := newRandomDataKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -203,13 +212,20 @@ func (s *SecretsService) newDataKey(ctx context.Context, name string, scope stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Store its encrypted value in db
|
// 3. Store its encrypted value in db
|
||||||
err = s.store.CreateDataKey(ctx, secrets.DataKey{
|
dek := secrets.DataKey{
|
||||||
Active: true, // TODO: right now we never mark a key as deactivated
|
Active: true, // TODO: right now we never mark a key as deactivated
|
||||||
Name: name,
|
Name: name,
|
||||||
Provider: s.currentProvider,
|
Provider: s.currentProvider,
|
||||||
EncryptedData: encrypted,
|
EncryptedData: encrypted,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if sess == nil {
|
||||||
|
err = s.store.CreateDataKey(ctx, dek)
|
||||||
|
} else {
|
||||||
|
err = s.store.CreateDataKeyWithDBSession(ctx, dek, sess)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,18 @@ import (
|
|||||||
// Service is an envelope encryption service in charge of encrypting/decrypting secrets.
|
// Service is an envelope encryption service in charge of encrypting/decrypting secrets.
|
||||||
// It is a replacement for encryption.Service
|
// It is a replacement for encryption.Service
|
||||||
type Service interface {
|
type Service interface {
|
||||||
|
// Encrypt MUST NOT be used within database transactions, it may cause database locks.
|
||||||
|
// For those specific use cases where the encryption operation cannot be moved outside
|
||||||
|
// the database transaction, look at database-specific methods present at the specific
|
||||||
|
// implementation present at manager.SecretsService.
|
||||||
Encrypt(ctx context.Context, payload []byte, opt EncryptionOptions) ([]byte, error)
|
Encrypt(ctx context.Context, payload []byte, opt EncryptionOptions) ([]byte, error)
|
||||||
Decrypt(ctx context.Context, payload []byte) ([]byte, error)
|
Decrypt(ctx context.Context, payload []byte) ([]byte, error)
|
||||||
|
|
||||||
|
// EncryptJsonData MUST NOT be used within database transactions.
|
||||||
|
// Look at Encrypt method comment for further details.
|
||||||
EncryptJsonData(ctx context.Context, kv map[string]string, opt EncryptionOptions) (map[string][]byte, error)
|
EncryptJsonData(ctx context.Context, kv map[string]string, opt EncryptionOptions) (map[string][]byte, error)
|
||||||
DecryptJsonData(ctx context.Context, sjd map[string][]byte) (map[string]string, error)
|
DecryptJsonData(ctx context.Context, sjd map[string][]byte) (map[string]string, error)
|
||||||
|
|
||||||
GetDecryptedValue(ctx context.Context, sjd map[string][]byte, key, fallback string) string
|
GetDecryptedValue(ctx context.Context, sjd map[string][]byte, key, fallback string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user