Files
bifrost/framework/configstore/encryption.go
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

370 lines
11 KiB
Go

package configstore
import (
"context"
"fmt"
"github.com/maximhq/bifrost/framework/configstore/tables"
"github.com/maximhq/bifrost/framework/encrypt"
"gorm.io/gorm"
)
const (
encryptionStatusPlainText = "plain_text"
encryptionStatusEncrypted = "encrypted"
encryptionBatchSize = 100
)
// EncryptPlaintextRows encrypts all rows with encryption_status='plain_text'
// across all sensitive tables. Called during startup when encryption is enabled.
// Each table's GORM BeforeSave hook handles the actual encryption.
func (s *RDBConfigStore) EncryptPlaintextRows(ctx context.Context) error {
if !encrypt.IsEnabled() {
return nil
}
var totalEncrypted int
// config_keys
count, err := s.encryptPlaintextKeys(ctx)
if err != nil {
return fmt.Errorf("failed to encrypt config_keys: %w", err)
}
totalEncrypted += count
// governance_virtual_keys
count, err = s.encryptPlaintextVirtualKeys(ctx)
if err != nil {
return fmt.Errorf("failed to encrypt virtual_keys: %w", err)
}
totalEncrypted += count
// sessions
count, err = s.encryptPlaintextSessions(ctx)
if err != nil {
return fmt.Errorf("failed to encrypt sessions: %w", err)
}
totalEncrypted += count
// oauth_tokens
count, err = s.encryptPlaintextOAuthTokens(ctx)
if err != nil {
return fmt.Errorf("failed to encrypt oauth_tokens: %w", err)
}
totalEncrypted += count
// oauth_configs
count, err = s.encryptPlaintextOAuthConfigs(ctx)
if err != nil {
return fmt.Errorf("failed to encrypt oauth_configs: %w", err)
}
totalEncrypted += count
// config_mcp_clients
count, err = s.encryptPlaintextMCPClients(ctx)
if err != nil {
return fmt.Errorf("failed to encrypt mcp_clients: %w", err)
}
totalEncrypted += count
// config_providers (proxy config)
count, err = s.encryptPlaintextProviderProxies(ctx)
if err != nil {
return fmt.Errorf("failed to encrypt provider proxy configs: %w", err)
}
totalEncrypted += count
// config_vector_store
count, err = s.encryptPlaintextVectorStoreConfigs(ctx)
if err != nil {
return fmt.Errorf("failed to encrypt vector_store configs: %w", err)
}
totalEncrypted += count
// config_plugins
count, err = s.encryptPlaintextPlugins(ctx)
if err != nil {
return fmt.Errorf("failed to encrypt plugin configs: %w", err)
}
totalEncrypted += count
if totalEncrypted > 0 && s.logger != nil {
s.logger.Info(fmt.Sprintf("encrypted %d plaintext rows across all tables", totalEncrypted))
}
return nil
}
// encryptPlaintextKeys finds all config_keys rows with plaintext encryption status and
// re-saves them in batches. The TableKey.BeforeSave hook handles the actual encryption.
func (s *RDBConfigStore) encryptPlaintextKeys(ctx context.Context) (int, error) {
var count int
for {
var keys []tables.TableKey
if err := s.DB().WithContext(ctx).
Where("encryption_status = ? OR encryption_status IS NULL OR encryption_status = ''", encryptionStatusPlainText).
Limit(encryptionBatchSize).
Find(&keys).Error; err != nil {
return count, err
}
if len(keys) == 0 {
break
}
if err := s.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for i := range keys {
if err := tx.Save(&keys[i]).Error; err != nil {
return err
}
}
return nil
}); err != nil {
return count, err
}
count += len(keys)
}
return count, nil
}
// encryptPlaintextVirtualKeys finds all governance_virtual_keys rows with plaintext encryption
// status and re-saves them in batches. The TableVirtualKey.BeforeSave hook handles encryption.
func (s *RDBConfigStore) encryptPlaintextVirtualKeys(ctx context.Context) (int, error) {
var count int
for {
var vks []tables.TableVirtualKey
if err := s.DB().WithContext(ctx).
Where("(encryption_status = ? OR encryption_status IS NULL OR encryption_status = '') AND value != ''", encryptionStatusPlainText).
Limit(encryptionBatchSize).
Find(&vks).Error; err != nil {
return count, err
}
if len(vks) == 0 {
break
}
if err := s.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for i := range vks {
if err := tx.Save(&vks[i]).Error; err != nil {
return err
}
}
return nil
}); err != nil {
return count, err
}
count += len(vks)
}
return count, nil
}
// encryptPlaintextSessions finds all sessions rows with plaintext encryption status and
// re-saves them in batches. The SessionsTable.BeforeSave hook handles encryption.
func (s *RDBConfigStore) encryptPlaintextSessions(ctx context.Context) (int, error) {
var count int
for {
var sessions []tables.SessionsTable
if err := s.DB().WithContext(ctx).
Where("(encryption_status = ? OR encryption_status IS NULL OR encryption_status = '') AND token != ''", encryptionStatusPlainText).
Limit(encryptionBatchSize).
Find(&sessions).Error; err != nil {
return count, err
}
if len(sessions) == 0 {
break
}
if err := s.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for i := range sessions {
if err := tx.Save(&sessions[i]).Error; err != nil {
return err
}
}
return nil
}); err != nil {
return count, err
}
count += len(sessions)
}
return count, nil
}
// encryptPlaintextOAuthTokens finds all oauth_tokens rows with plaintext encryption status
// and re-saves them in batches. The TableOauthToken.BeforeSave hook handles encryption.
func (s *RDBConfigStore) encryptPlaintextOAuthTokens(ctx context.Context) (int, error) {
var count int
for {
var tokens []tables.TableOauthToken
if err := s.DB().WithContext(ctx).
Where("encryption_status = ? OR encryption_status IS NULL OR encryption_status = ''", encryptionStatusPlainText).
Limit(encryptionBatchSize).
Find(&tokens).Error; err != nil {
return count, err
}
if len(tokens) == 0 {
break
}
if err := s.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for i := range tokens {
if err := tx.Save(&tokens[i]).Error; err != nil {
return err
}
}
return nil
}); err != nil {
return count, err
}
count += len(tokens)
}
return count, nil
}
// encryptPlaintextOAuthConfigs finds all oauth_configs rows with plaintext encryption status
// and re-saves them in batches. The TableOauthConfig.BeforeSave hook handles encryption.
func (s *RDBConfigStore) encryptPlaintextOAuthConfigs(ctx context.Context) (int, error) {
var count int
for {
var configs []tables.TableOauthConfig
if err := s.DB().WithContext(ctx).
Where("(encryption_status = ? OR encryption_status IS NULL OR encryption_status = '') AND (client_secret != '' OR code_verifier != '')", encryptionStatusPlainText).
Limit(encryptionBatchSize).
Find(&configs).Error; err != nil {
return count, err
}
if len(configs) == 0 {
break
}
if err := s.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for i := range configs {
if err := tx.Save(&configs[i]).Error; err != nil {
return err
}
}
return nil
}); err != nil {
return count, err
}
count += len(configs)
}
return count, nil
}
// encryptPlaintextMCPClients finds all config_mcp_clients rows with plaintext encryption
// status and re-saves them in batches. The TableMCPClient.BeforeSave hook handles encryption.
func (s *RDBConfigStore) encryptPlaintextMCPClients(ctx context.Context) (int, error) {
var count int
for {
var clients []tables.TableMCPClient
if err := s.DB().WithContext(ctx).
Where("encryption_status = ? OR encryption_status IS NULL OR encryption_status = ''", encryptionStatusPlainText).
Limit(encryptionBatchSize).
Find(&clients).Error; err != nil {
return count, err
}
if len(clients) == 0 {
break
}
if err := s.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for i := range clients {
if err := tx.Save(&clients[i]).Error; err != nil {
return err
}
}
return nil
}); err != nil {
return count, err
}
count += len(clients)
}
return count, nil
}
// encryptPlaintextProviderProxies finds all config_providers rows that have a non-empty
// proxy config with plaintext encryption status and re-saves them in batches. The
// TableProvider.BeforeSave hook handles encryption.
func (s *RDBConfigStore) encryptPlaintextProviderProxies(ctx context.Context) (int, error) {
var count int
for {
var providers []tables.TableProvider
if err := s.DB().WithContext(ctx).
Where("(encryption_status = ? OR encryption_status IS NULL OR encryption_status = '') AND proxy_config_json != '' AND proxy_config_json IS NOT NULL", encryptionStatusPlainText).
Limit(encryptionBatchSize).
Find(&providers).Error; err != nil {
return count, err
}
if len(providers) == 0 {
break
}
if err := s.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for i := range providers {
if err := tx.Save(&providers[i]).Error; err != nil {
return err
}
}
return nil
}); err != nil {
return count, err
}
count += len(providers)
}
return count, nil
}
// encryptPlaintextVectorStoreConfigs finds all config_vector_store rows that have a non-empty
// config with plaintext encryption status and re-saves them in batches. The
// TableVectorStoreConfig.BeforeSave hook handles encryption.
func (s *RDBConfigStore) encryptPlaintextVectorStoreConfigs(ctx context.Context) (int, error) {
var count int
for {
var configs []tables.TableVectorStoreConfig
if err := s.DB().WithContext(ctx).
Where("(encryption_status = ? OR encryption_status IS NULL OR encryption_status = '') AND config IS NOT NULL AND config != ''", encryptionStatusPlainText).
Limit(encryptionBatchSize).
Find(&configs).Error; err != nil {
return count, err
}
if len(configs) == 0 {
break
}
if err := s.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for i := range configs {
if err := tx.Save(&configs[i]).Error; err != nil {
return err
}
}
return nil
}); err != nil {
return count, err
}
count += len(configs)
}
return count, nil
}
// encryptPlaintextPlugins finds all config_plugins rows that have a non-empty config with
// plaintext encryption status and re-saves them in batches. The TablePlugin.BeforeSave hook
// handles encryption.
func (s *RDBConfigStore) encryptPlaintextPlugins(ctx context.Context) (int, error) {
var count int
for {
var plugins []tables.TablePlugin
if err := s.DB().WithContext(ctx).
Where("(encryption_status = ? OR encryption_status IS NULL OR encryption_status = '') AND config_json != '' AND config_json != '{}'", encryptionStatusPlainText).
Limit(encryptionBatchSize).
Find(&plugins).Error; err != nil {
return count, err
}
if len(plugins) == 0 {
break
}
if err := s.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
for i := range plugins {
if err := tx.Save(&plugins[i]).Error; err != nil {
return err
}
}
return nil
}); err != nil {
return count, err
}
count += len(plugins)
}
return count, nil
}