From 068369c04089994b451809fe51095838649fd527 Mon Sep 17 00:00:00 2001 From: chohee Date: Sat, 9 Aug 2025 11:03:10 +0800 Subject: [PATCH 1/7] feat: add encryption key management Signed-off-by: chohee --- cmd/dependency/dependency.go | 4 +- cmd/manager/cmd/root.go | 8 ++ cmd/scheduler/cmd/root.go | 4 + go.mod | 3 + go.sum | 2 - manager/config/config.go | 44 ++++++++++ manager/database/database.go | 1 + manager/manager.go | 116 +++++++++++++++++++++++++ manager/models/encryption.go | 6 ++ manager/rpcserver/manager_server_v2.go | 16 ++++ scheduler/config/config.go | 3 + 11 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 manager/models/encryption.go diff --git a/cmd/dependency/dependency.go b/cmd/dependency/dependency.go index 7cd600d23c8..fdb2aab6be6 100644 --- a/cmd/dependency/dependency.go +++ b/cmd/dependency/dependency.go @@ -57,6 +57,7 @@ import ( "d7y.io/dragonfly/v2/pkg/types" "d7y.io/dragonfly/v2/pkg/unit" "d7y.io/dragonfly/v2/version" + manager_cfg "d7y.io/dragonfly/v2/manager/config" ) // InitCommandAndConfig initializes flags binding and common sub cmds. @@ -307,7 +308,8 @@ func initDecoderConfig(dc *mapstructure.DecoderConfig) { reflect.TypeOf(config.URL{}), reflect.TypeOf(net.IP{}), reflect.TypeOf(config.CertPool{}), - reflect.TypeOf(config.Regexp{}): + reflect.TypeOf(config.Regexp{}), + reflect.TypeOf(manager_cfg.EncryptionKey{}): b, _ := yaml.Marshal(v) p := reflect.New(to) diff --git a/cmd/manager/cmd/root.go b/cmd/manager/cmd/root.go index 224f97719eb..c804af35638 100644 --- a/cmd/manager/cmd/root.go +++ b/cmd/manager/cmd/root.go @@ -101,6 +101,10 @@ func init() { // initDfpath initializes dfpath. func initDfpath(cfg *config.ServerConfig) (dfpath.Dfpath, error) { var options []dfpath.Option + if cfg.WorkHome != "" { + options = append(options, dfpath.WithWorkHome(cfg.WorkHome)) + } + if cfg.LogDir != "" { options = append(options, dfpath.WithLogDir(cfg.LogDir)) } @@ -113,6 +117,10 @@ func initDfpath(cfg *config.ServerConfig) (dfpath.Dfpath, error) { options = append(options, dfpath.WithPluginDir(cfg.PluginDir)) } + if cfg.DataDir != "" { + options = append(options, dfpath.WithDataDir(cfg.DataDir)) + } + return dfpath.New(options...) } diff --git a/cmd/scheduler/cmd/root.go b/cmd/scheduler/cmd/root.go index 6a5c1629d3a..8ca839cc9cf 100644 --- a/cmd/scheduler/cmd/root.go +++ b/cmd/scheduler/cmd/root.go @@ -100,6 +100,10 @@ func init() { func initDfpath(cfg *config.ServerConfig) (dfpath.Dfpath, error) { var options []dfpath.Option + if cfg.WorkHome != "" { + options = append(options, dfpath.WithWorkHome(cfg.WorkHome)) + } + if cfg.LogDir != "" { options = append(options, dfpath.WithLogDir(cfg.LogDir)) } diff --git a/go.mod b/go.mod index a5db61668ea..cf41472baeb 100644 --- a/go.mod +++ b/go.mod @@ -280,3 +280,6 @@ require ( gorm.io/driver/sqlserver v1.4.1 // indirect gorm.io/plugin/dbresolver v1.3.0 // indirect ) + +// use local api repo +replace d7y.io/api/v2 => ../api_fork diff --git a/go.sum b/go.sum index 73ea59b9b92..d15d33a1ef1 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,6 @@ cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6Q cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= -d7y.io/api/v2 v2.1.60 h1:Imk1mw30wmTJNdqKFF5VO/eqI/t1H5PfIPPK4ryKX2U= -d7y.io/api/v2 v2.1.60/go.mod h1:WzakPywEgs27gr/TwrlRarPRRK2o5nN0YrTzHDdFii8= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= diff --git a/manager/config/config.go b/manager/config/config.go index 5576dda57c3..65a6af84f43 100644 --- a/manager/config/config.go +++ b/manager/config/config.go @@ -17,12 +17,14 @@ package config import ( + "encoding/base64" "errors" "fmt" "net" "time" "d7y.io/dragonfly/v2/cmd/dependency/base" + logger "d7y.io/dragonfly/v2/internal/dflog" "d7y.io/dragonfly/v2/pkg/net/ip" "d7y.io/dragonfly/v2/pkg/objectstorage" "d7y.io/dragonfly/v2/pkg/slices" @@ -56,12 +58,18 @@ type Config struct { // Network configuration. Network NetworkConfig `yaml:"network" mapstructure:"network"` + + // Encryption configuration + Encryption EncryptionConfig `yaml:"encryption" mapstructure:"encryption"` } type ServerConfig struct { // Server name. Name string `yaml:"name" mapstructure:"name"` + // Server work home directory. + WorkHome string `yaml:"workHome" mapstructure:"workHome"` + // Server dynamic config cache directory. CacheDir string `yaml:"cacheDir" mapstructure:"cacheDir"` @@ -83,6 +91,9 @@ type ServerConfig struct { // Server plugin directory. PluginDir string `yaml:"pluginDir" mapstructure:"pluginDir"` + // Server data directory. + DataDir string `yaml:"dataDir" mapstructure:"dataDir"` + // GRPC server configuration. GRPC GRPCConfig `yaml:"grpc" mapstructure:"grpc"` @@ -398,6 +409,34 @@ type NetworkConfig struct { EnableIPv6 bool `mapstructure:"enableIPv6" yaml:"enableIPv6"` } +type EncryptionKey [32]byte +type EncryptionConfig struct { + Enable bool `mapstructure:"enable" yaml:"enable"` + // AES256 base64, optional + Key *EncryptionKey `mapstructure:"key" yaml:"key"` +} + +// UnmarshalText Base64 +func (e *EncryptionKey) UnmarshalText(text []byte) error { + logger.Infof("Base64 str: %s", string(text)) + keyBytes, err := base64.StdEncoding.DecodeString(string(text)) + if err != nil { + return fmt.Errorf("invalid base64 key: %v", err) + } + + if len(keyBytes) != 32 { + return fmt.Errorf("key must be 32 bytes, got %d", len(keyBytes)) + } + + copy(e[:], keyBytes) + return nil +} + +// MarshalText Base64 +func (e EncryptionKey) MarshalText() ([]byte, error) { + return []byte(base64.StdEncoding.EncodeToString(e[:])), nil +} + // New config instance. func New() *Config { return &Config{ @@ -489,6 +528,9 @@ func New() *Config { Network: NetworkConfig{ EnableIPv6: DefaultNetworkEnableIPv6, }, + Encryption: EncryptionConfig{ + Enable: false, + }, } } @@ -686,6 +728,8 @@ func (cfg *Config) Validate() error { } } + // TODO: validate key + return nil } diff --git a/manager/database/database.go b/manager/database/database.go index 471d8c6ca29..29de6fd7012 100644 --- a/manager/database/database.go +++ b/manager/database/database.go @@ -115,6 +115,7 @@ func migrate(db *gorm.DB) error { &models.Application{}, &models.PersonalAccessToken{}, &models.Peer{}, + &models.EncryptionKey{}, ) } diff --git a/manager/manager.go b/manager/manager.go index d1a37314aad..8223c8373e3 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -18,7 +18,11 @@ package manager import ( "context" + "crypto/rand" "embed" + "encoding/base64" + "encoding/hex" + "errors" "fmt" "io/fs" "net" @@ -29,6 +33,8 @@ import ( "google.golang.org/grpc" "gorm.io/gorm" + "bytes" + logger "d7y.io/dragonfly/v2/internal/dflog" "d7y.io/dragonfly/v2/internal/ratelimiter" "d7y.io/dragonfly/v2/manager/cache" @@ -37,6 +43,7 @@ import ( managergc "d7y.io/dragonfly/v2/manager/gc" "d7y.io/dragonfly/v2/manager/job" "d7y.io/dragonfly/v2/manager/metrics" + "d7y.io/dragonfly/v2/manager/models" "d7y.io/dragonfly/v2/manager/permission/rbac" "d7y.io/dragonfly/v2/manager/router" "d7y.io/dragonfly/v2/manager/rpcserver" @@ -48,6 +55,7 @@ import ( "d7y.io/dragonfly/v2/pkg/objectstorage" "d7y.io/dragonfly/v2/pkg/redis" "d7y.io/dragonfly/v2/pkg/rpc" + "gorm.io/plugin/soft_delete" ) const ( @@ -122,6 +130,16 @@ func New(cfg *config.Config, d dfpath.Dfpath) (*Server, error) { return nil, err } + // Initialize encryption key + if cfg.Encryption.Enable { + logger.Infof("Encryption enabled") + if err := initializeEncryptionKey(cfg, db.DB); err != nil { + return nil, err + } + } else { + logger.Infof("Encryption disabled") + } + // Initialize enforcer. enforcer, err := rbac.NewEnforcer(db.DB) if err != nil { @@ -250,6 +268,104 @@ func registerGCTasks(gc pkggc.GC, db *gorm.DB) error { return nil } +// initializeEncryptionKey +func initializeEncryptionKey(cfg *config.Config, db *gorm.DB) error { + // db.Delete(&models.EncryptionKey{}, "1 = 1") + + var existingKey models.EncryptionKey + hasDBKey := false + if err := db.First(&existingKey).Error; err == nil { + hasDBKey = true + } else if !errors.Is(err, gorm.ErrRecordNotFound) { + return fmt.Errorf("failed to check encryption key: %v", err) + } + + if cfg.Encryption.Key != nil { + configKey := cfg.Encryption.Key + keyBytes := configKey[:] + if hasDBKey { + // compare key in config with key in db + if bytes.Equal(existingKey.Key, keyBytes) { + logger.Infof( + "encryption key in config file is the same as in database, key(hex): %s, key(base64): %s", + hex.EncodeToString(keyBytes), + base64.StdEncoding.EncodeToString(keyBytes), + ) + return nil + } + // key in config is different from key in db, overwrite db + oldKeyHex := hex.EncodeToString(existingKey.Key) + oldKeyBase64 := base64.StdEncoding.EncodeToString(existingKey.Key) + newKeyHex := hex.EncodeToString(keyBytes) + newKeyBase64 := base64.StdEncoding.EncodeToString(keyBytes) + + if err := db.Model(&existingKey).Update("key", keyBytes).Error; err != nil { + return fmt.Errorf("failed to update encryption key in database: %v", err) + } + + logger.Infof( + "encryption key in database is overwritten by config file, old key(hex): %s, old key(base64): %s, new key(hex): %s, new key(base64): %s", + oldKeyHex, oldKeyBase64, newKeyHex, newKeyBase64, + ) + return nil + } else { + // config has key, but db has no key, write it into db + // check soft delete + var oldKey models.EncryptionKey + if err := db.Unscoped().Where("`key` = ?", keyBytes).First(&oldKey).Error; err == nil { + if oldKey.IsDel != soft_delete.DeletedAt(soft_delete.FlagActived) { + // restore the key soft deleted + db.Unscoped().Model(&oldKey).Update("is_del", soft_delete.FlagActived) + logger.Infof("Restore the key which was soft deleted before") + } else { + logger.Fatalf("key should be soft deleted in this situation") + } + } else if errors.Is(err, gorm.ErrRecordNotFound) { + // insert new key + if err := db.Create(&models.EncryptionKey{Key: keyBytes}).Error; err != nil { + return fmt.Errorf("failed to save encryption key to database: %v", err) + } + } else { + // return fmt.Errorf("unknow failed when update encryption key in database: %v", err) + logger.Fatalf("unknow failed when update encryption key in database: %v", err) + // panic(err) + } + + logger.Infof( + "encryption key from config file is saved to database, key(hex): %s, key(base64): %s", + hex.EncodeToString(keyBytes), + base64.StdEncoding.EncodeToString(keyBytes), + ) + return nil + } + } + + // config has no key and db has key + if hasDBKey { + logger.Infof( + "encryption key loaded from database, key(hex): %s, key(base64): %s", + hex.EncodeToString(existingKey.Key), + base64.StdEncoding.EncodeToString(existingKey.Key), + ) + return nil + } + + // config and db both have no key, generate one + keyBytes := make([]byte, 32) + if _, err := rand.Read(keyBytes); err != nil { + return fmt.Errorf("failed to generate random encryption key: %v", err) + } + if err := db.Create(&models.EncryptionKey{Key: keyBytes}).Error; err != nil { + return fmt.Errorf("failed to save random encryption key to database: %v", err) + } + logger.Infof( + "generated random encryption key and saved to database, key(hex): %s, key(base64): %s", + hex.EncodeToString(keyBytes), + base64.StdEncoding.EncodeToString(keyBytes), + ) + return nil +} + // Serve starts the manager server. func (s *Server) Serve() error { // Started REST server. diff --git a/manager/models/encryption.go b/manager/models/encryption.go new file mode 100644 index 00000000000..3ee480a555f --- /dev/null +++ b/manager/models/encryption.go @@ -0,0 +1,6 @@ +package models + +type EncryptionKey struct { + BaseModel + Key []byte `gorm:"type:binary(32);not null;unique" json:"key"` +} diff --git a/manager/rpcserver/manager_server_v2.go b/manager/rpcserver/manager_server_v2.go index b169bd62796..290fcde8f1e 100644 --- a/manager/rpcserver/manager_server_v2.go +++ b/manager/rpcserver/manager_server_v2.go @@ -976,3 +976,19 @@ func (s *managerServerV2) KeepAlive(stream managerv2.Manager_KeepAliveServer) er } } } + +// RequestEncryptionKey implements manager.ManagerServer. +func (s *managerServerV2) RequestEncryptionKey(ctx context.Context, req *managerv2.RequestEncryptionKeyRequest) (*managerv2.RequestEncryptionKeyResponse, error) { + log := logger.WithHostnameAndIP(req.Hostname, req.Ip) + + // Get key from db + var encKey models.EncryptionKey + if err := s.db.WithContext(ctx).First(&encKey).Error; err != nil { + log.Errorf("failed to get encryption key: %v", err) + return nil, status.Error(codes.Internal, "failed to get encryption key") + } + + return &managerv2.RequestEncryptionKeyResponse{ + EncryptionKey: encKey.Key, + }, nil +} diff --git a/scheduler/config/config.go b/scheduler/config/config.go index 87a84e40957..0942af592fb 100644 --- a/scheduler/config/config.go +++ b/scheduler/config/config.go @@ -85,6 +85,9 @@ type ServerConfig struct { // TLS server configuration. TLS *GRPCTLSServerConfig `yaml:"tls" mapstructure:"tls"` + // Server work home directory. + WorkHome string `yaml:"workHome" mapstructure:"workHome"` + // Server dynamic config cache directory. CacheDir string `yaml:"cacheDir" mapstructure:"cacheDir"` From d2e916dbf5ebc78a701a126a8b4915c95116282d Mon Sep 17 00:00:00 2001 From: chohee Date: Wed, 27 Aug 2025 16:38:15 +0800 Subject: [PATCH 2/7] refactor: rearrange key initialization and normalize log - Reduce nesting in initializeEncryptionKey - Normalize log messages to lowercase Signed-off-by: chohee --- manager/config/config.go | 3 +- manager/manager.go | 133 ++++++++++++++++++++------------------- 2 files changed, 72 insertions(+), 64 deletions(-) diff --git a/manager/config/config.go b/manager/config/config.go index 65a6af84f43..f5ad7ec749e 100644 --- a/manager/config/config.go +++ b/manager/config/config.go @@ -418,7 +418,8 @@ type EncryptionConfig struct { // UnmarshalText Base64 func (e *EncryptionKey) UnmarshalText(text []byte) error { - logger.Infof("Base64 str: %s", string(text)) + // TODO: avoid printing key + logger.Infof("base64 key str: %s", string(text)) keyBytes, err := base64.StdEncoding.DecodeString(string(text)) if err != nil { return fmt.Errorf("invalid base64 key: %v", err) diff --git a/manager/manager.go b/manager/manager.go index 8223c8373e3..6beb772f9ef 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -132,12 +132,12 @@ func New(cfg *config.Config, d dfpath.Dfpath) (*Server, error) { // Initialize encryption key if cfg.Encryption.Enable { - logger.Infof("Encryption enabled") + logger.Infof("encryption enabled") if err := initializeEncryptionKey(cfg, db.DB); err != nil { return nil, err } } else { - logger.Infof("Encryption disabled") + logger.Infof("encryption disabled") } // Initialize enforcer. @@ -272,6 +272,8 @@ func registerGCTasks(gc pkggc.GC, db *gorm.DB) error { func initializeEncryptionKey(cfg *config.Config, db *gorm.DB) error { // db.Delete(&models.EncryptionKey{}, "1 = 1") + // TODO: manually use cache or gorm auto cache? + // TODO: avoid printing key var existingKey models.EncryptionKey hasDBKey := false if err := db.First(&existingKey).Error; err == nil { @@ -280,86 +282,91 @@ func initializeEncryptionKey(cfg *config.Config, db *gorm.DB) error { return fmt.Errorf("failed to check encryption key: %v", err) } - if cfg.Encryption.Key != nil { - configKey := cfg.Encryption.Key - keyBytes := configKey[:] + // 1. no key in config + if cfg.Encryption.Key == nil { + // 1.1 config has no key and db has key if hasDBKey { - // compare key in config with key in db - if bytes.Equal(existingKey.Key, keyBytes) { - logger.Infof( - "encryption key in config file is the same as in database, key(hex): %s, key(base64): %s", - hex.EncodeToString(keyBytes), - base64.StdEncoding.EncodeToString(keyBytes), - ) - return nil - } - // key in config is different from key in db, overwrite db - oldKeyHex := hex.EncodeToString(existingKey.Key) - oldKeyBase64 := base64.StdEncoding.EncodeToString(existingKey.Key) - newKeyHex := hex.EncodeToString(keyBytes) - newKeyBase64 := base64.StdEncoding.EncodeToString(keyBytes) - - if err := db.Model(&existingKey).Update("key", keyBytes).Error; err != nil { - return fmt.Errorf("failed to update encryption key in database: %v", err) - } - logger.Infof( - "encryption key in database is overwritten by config file, old key(hex): %s, old key(base64): %s, new key(hex): %s, new key(base64): %s", - oldKeyHex, oldKeyBase64, newKeyHex, newKeyBase64, + "encryption key loaded from database, key(hex): %s, key(base64): %s", + hex.EncodeToString(existingKey.Key), + base64.StdEncoding.EncodeToString(existingKey.Key), ) return nil - } else { - // config has key, but db has no key, write it into db - // check soft delete - var oldKey models.EncryptionKey - if err := db.Unscoped().Where("`key` = ?", keyBytes).First(&oldKey).Error; err == nil { - if oldKey.IsDel != soft_delete.DeletedAt(soft_delete.FlagActived) { - // restore the key soft deleted - db.Unscoped().Model(&oldKey).Update("is_del", soft_delete.FlagActived) - logger.Infof("Restore the key which was soft deleted before") - } else { - logger.Fatalf("key should be soft deleted in this situation") - } - } else if errors.Is(err, gorm.ErrRecordNotFound) { - // insert new key - if err := db.Create(&models.EncryptionKey{Key: keyBytes}).Error; err != nil { - return fmt.Errorf("failed to save encryption key to database: %v", err) - } - } else { - // return fmt.Errorf("unknow failed when update encryption key in database: %v", err) - logger.Fatalf("unknow failed when update encryption key in database: %v", err) - // panic(err) - } + } + + // 1.2 config and db both have no key, generate one + keyBytes := make([]byte, 32) + if _, err := rand.Read(keyBytes); err != nil { + return fmt.Errorf("failed to generate random encryption key: %v", err) + } + if err := db.Create(&models.EncryptionKey{Key: keyBytes}).Error; err != nil { + return fmt.Errorf("failed to save random encryption key to database: %v", err) + } + logger.Infof( + "generated random encryption key and saved to database, key(hex): %s, key(base64): %s", + hex.EncodeToString(keyBytes), + base64.StdEncoding.EncodeToString(keyBytes), + ) + return nil + } + configKey := cfg.Encryption.Key + keyBytes := configKey[:] + // 2. have key in config + // 2.1 have key in db + if hasDBKey { + // compare key in config with key in db + if bytes.Equal(existingKey.Key, keyBytes) { logger.Infof( - "encryption key from config file is saved to database, key(hex): %s, key(base64): %s", + "encryption key in config file is the same as in database, key(hex): %s, key(base64): %s", hex.EncodeToString(keyBytes), base64.StdEncoding.EncodeToString(keyBytes), ) return nil } - } + // key in config is different from key in db, update config key into db + oldKeyHex := hex.EncodeToString(existingKey.Key) + oldKeyBase64 := base64.StdEncoding.EncodeToString(existingKey.Key) + newKeyHex := hex.EncodeToString(keyBytes) + newKeyBase64 := base64.StdEncoding.EncodeToString(keyBytes) + + if err := db.Model(&existingKey).Update("key", keyBytes).Error; err != nil { + return fmt.Errorf("failed to update encryption key in database: %v", err) + } - // config has no key and db has key - if hasDBKey { logger.Infof( - "encryption key loaded from database, key(hex): %s, key(base64): %s", - hex.EncodeToString(existingKey.Key), - base64.StdEncoding.EncodeToString(existingKey.Key), + "encryption key in database is overwritten by config file, old key(hex): %s, old key(base64): %s, new key(hex): %s, new key(base64): %s", + oldKeyHex, oldKeyBase64, newKeyHex, newKeyBase64, ) return nil } - // config and db both have no key, generate one - keyBytes := make([]byte, 32) - if _, err := rand.Read(keyBytes); err != nil { - return fmt.Errorf("failed to generate random encryption key: %v", err) - } - if err := db.Create(&models.EncryptionKey{Key: keyBytes}).Error; err != nil { - return fmt.Errorf("failed to save random encryption key to database: %v", err) + // 2.2 db has no key(may soft-deleted), config has key, write config's key into db + var oldKey models.EncryptionKey + // check soft-deleted old same key + err := db.Unscoped().Where("`key` = ?", keyBytes).First(&oldKey).Error + // old same key not found + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + logger.Fatalf("unknow failed when update encryption key in database: %v", err) + } + // not find same old key, so we can insert a new key + if err := db.Create(&models.EncryptionKey{Key: keyBytes}).Error; err != nil { + return fmt.Errorf("failed to save encryption key to database: %v", err) + } + } else { + // find old same key + // if it is not sofe-deleted, that is fatal + if oldKey.IsDel == soft_delete.DeletedAt(soft_delete.FlagActived) { + logger.Fatalf("key should be soft deleted in this situation") + } + // restore old key + db.Unscoped().Model(&oldKey).Update("is_del", soft_delete.FlagActived) + logger.Infof("restore the key which was soft deleted before") } + logger.Infof( - "generated random encryption key and saved to database, key(hex): %s, key(base64): %s", + "encryption key from config file is saved to database, key(hex): %s, key(base64): %s", hex.EncodeToString(keyBytes), base64.StdEncoding.EncodeToString(keyBytes), ) From 32cc5955d36085ddee0c5575308e6eae144df569 Mon Sep 17 00:00:00 2001 From: chohee Date: Wed, 27 Aug 2025 16:40:37 +0800 Subject: [PATCH 3/7] fix: return response with error when encryption disabled - Add response with error when encryption is disabled but clients request key Signed-off-by: chohee --- manager/rpcserver/manager_server_v2.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/manager/rpcserver/manager_server_v2.go b/manager/rpcserver/manager_server_v2.go index 290fcde8f1e..4077c2c65d7 100644 --- a/manager/rpcserver/manager_server_v2.go +++ b/manager/rpcserver/manager_server_v2.go @@ -980,7 +980,10 @@ func (s *managerServerV2) KeepAlive(stream managerv2.Manager_KeepAliveServer) er // RequestEncryptionKey implements manager.ManagerServer. func (s *managerServerV2) RequestEncryptionKey(ctx context.Context, req *managerv2.RequestEncryptionKeyRequest) (*managerv2.RequestEncryptionKeyResponse, error) { log := logger.WithHostnameAndIP(req.Hostname, req.Ip) - + if !s.config.Encryption.Enable { + // TODO: new error code in drgonfly-api? + return nil, status.Error(codes.Unavailable, "encryption is disabled") + } // Get key from db var encKey models.EncryptionKey if err := s.db.WithContext(ctx).First(&encKey).Error; err != nil { From f18b00da1cf7ddf248c755ac1d1a68633649d260 Mon Sep 17 00:00:00 2001 From: chohee Date: Mon, 1 Sep 2025 23:38:36 +0800 Subject: [PATCH 4/7] chore: clean comments and move log Signed-off-by: chohee --- manager/manager.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/manager/manager.go b/manager/manager.go index 6beb772f9ef..4a2a496f9e5 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -132,10 +132,10 @@ func New(cfg *config.Config, d dfpath.Dfpath) (*Server, error) { // Initialize encryption key if cfg.Encryption.Enable { - logger.Infof("encryption enabled") if err := initializeEncryptionKey(cfg, db.DB); err != nil { return nil, err } + logger.Infof("encryption enabled") } else { logger.Infof("encryption disabled") } @@ -270,10 +270,6 @@ func registerGCTasks(gc pkggc.GC, db *gorm.DB) error { // initializeEncryptionKey func initializeEncryptionKey(cfg *config.Config, db *gorm.DB) error { - // db.Delete(&models.EncryptionKey{}, "1 = 1") - - // TODO: manually use cache or gorm auto cache? - // TODO: avoid printing key var existingKey models.EncryptionKey hasDBKey := false if err := db.First(&existingKey).Error; err == nil { From 3ba0e577f2916bfca904a3145351ff022cfb86bd Mon Sep 17 00:00:00 2001 From: chohee Date: Wed, 10 Sep 2025 12:35:24 +0800 Subject: [PATCH 5/7] feat: manager decide whether to enable encryption Signed-off-by: chohee --- manager/manager.go | 99 ++++---------------------- manager/rpcserver/manager_server_v2.go | 6 +- 2 files changed, 16 insertions(+), 89 deletions(-) diff --git a/manager/manager.go b/manager/manager.go index 4a2a496f9e5..5252dfbcd46 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -33,8 +33,6 @@ import ( "google.golang.org/grpc" "gorm.io/gorm" - "bytes" - logger "d7y.io/dragonfly/v2/internal/dflog" "d7y.io/dragonfly/v2/internal/ratelimiter" "d7y.io/dragonfly/v2/manager/cache" @@ -55,7 +53,6 @@ import ( "d7y.io/dragonfly/v2/pkg/objectstorage" "d7y.io/dragonfly/v2/pkg/redis" "d7y.io/dragonfly/v2/pkg/rpc" - "gorm.io/plugin/soft_delete" ) const ( @@ -270,99 +267,27 @@ func registerGCTasks(gc pkggc.GC, db *gorm.DB) error { // initializeEncryptionKey func initializeEncryptionKey(cfg *config.Config, db *gorm.DB) error { + // 1. try get key from db var existingKey models.EncryptionKey - hasDBKey := false if err := db.First(&existingKey).Error; err == nil { - hasDBKey = true + logger.Infof("encryption key loaded from database, key(hex): %s, key(base64): %s", + hex.EncodeToString(existingKey.Key), + base64.StdEncoding.EncodeToString(existingKey.Key), + ) } else if !errors.Is(err, gorm.ErrRecordNotFound) { return fmt.Errorf("failed to check encryption key: %v", err) } - // 1. no key in config - if cfg.Encryption.Key == nil { - // 1.1 config has no key and db has key - if hasDBKey { - logger.Infof( - "encryption key loaded from database, key(hex): %s, key(base64): %s", - hex.EncodeToString(existingKey.Key), - base64.StdEncoding.EncodeToString(existingKey.Key), - ) - return nil - } - - // 1.2 config and db both have no key, generate one - keyBytes := make([]byte, 32) - if _, err := rand.Read(keyBytes); err != nil { - return fmt.Errorf("failed to generate random encryption key: %v", err) - } - if err := db.Create(&models.EncryptionKey{Key: keyBytes}).Error; err != nil { - return fmt.Errorf("failed to save random encryption key to database: %v", err) - } - logger.Infof( - "generated random encryption key and saved to database, key(hex): %s, key(base64): %s", - hex.EncodeToString(keyBytes), - base64.StdEncoding.EncodeToString(keyBytes), - ) - return nil - } - - configKey := cfg.Encryption.Key - keyBytes := configKey[:] - // 2. have key in config - // 2.1 have key in db - if hasDBKey { - // compare key in config with key in db - if bytes.Equal(existingKey.Key, keyBytes) { - logger.Infof( - "encryption key in config file is the same as in database, key(hex): %s, key(base64): %s", - hex.EncodeToString(keyBytes), - base64.StdEncoding.EncodeToString(keyBytes), - ) - return nil - } - // key in config is different from key in db, update config key into db - oldKeyHex := hex.EncodeToString(existingKey.Key) - oldKeyBase64 := base64.StdEncoding.EncodeToString(existingKey.Key) - newKeyHex := hex.EncodeToString(keyBytes) - newKeyBase64 := base64.StdEncoding.EncodeToString(keyBytes) - - if err := db.Model(&existingKey).Update("key", keyBytes).Error; err != nil { - return fmt.Errorf("failed to update encryption key in database: %v", err) - } - - logger.Infof( - "encryption key in database is overwritten by config file, old key(hex): %s, old key(base64): %s, new key(hex): %s, new key(base64): %s", - oldKeyHex, oldKeyBase64, newKeyHex, newKeyBase64, - ) - return nil + // 2. if there is no key in db, generate a new one + keyBytes := make([]byte, 32) + if _, err := rand.Read(keyBytes); err != nil { + return fmt.Errorf("failed to generate random encryption key: %v", err) } - - // 2.2 db has no key(may soft-deleted), config has key, write config's key into db - var oldKey models.EncryptionKey - // check soft-deleted old same key - err := db.Unscoped().Where("`key` = ?", keyBytes).First(&oldKey).Error - // old same key not found - if err != nil { - if !errors.Is(err, gorm.ErrRecordNotFound) { - logger.Fatalf("unknow failed when update encryption key in database: %v", err) - } - // not find same old key, so we can insert a new key - if err := db.Create(&models.EncryptionKey{Key: keyBytes}).Error; err != nil { - return fmt.Errorf("failed to save encryption key to database: %v", err) - } - } else { - // find old same key - // if it is not sofe-deleted, that is fatal - if oldKey.IsDel == soft_delete.DeletedAt(soft_delete.FlagActived) { - logger.Fatalf("key should be soft deleted in this situation") - } - // restore old key - db.Unscoped().Model(&oldKey).Update("is_del", soft_delete.FlagActived) - logger.Infof("restore the key which was soft deleted before") + if err := db.Create(&models.EncryptionKey{Key: keyBytes}).Error; err != nil { + return fmt.Errorf("failed to save random encryption key to database: %v", err) } - logger.Infof( - "encryption key from config file is saved to database, key(hex): %s, key(base64): %s", + "generated random encryption key and saved to database, key(hex): %s, key(base64): %s", hex.EncodeToString(keyBytes), base64.StdEncoding.EncodeToString(keyBytes), ) diff --git a/manager/rpcserver/manager_server_v2.go b/manager/rpcserver/manager_server_v2.go index 4077c2c65d7..98da1353e5b 100644 --- a/manager/rpcserver/manager_server_v2.go +++ b/manager/rpcserver/manager_server_v2.go @@ -981,8 +981,9 @@ func (s *managerServerV2) KeepAlive(stream managerv2.Manager_KeepAliveServer) er func (s *managerServerV2) RequestEncryptionKey(ctx context.Context, req *managerv2.RequestEncryptionKeyRequest) (*managerv2.RequestEncryptionKeyResponse, error) { log := logger.WithHostnameAndIP(req.Hostname, req.Ip) if !s.config.Encryption.Enable { - // TODO: new error code in drgonfly-api? - return nil, status.Error(codes.Unavailable, "encryption is disabled") + return &managerv2.RequestEncryptionKeyResponse{ + Status: managerv2.EncryptionStatus_ENCRYPTION_DISABLED, + }, nil } // Get key from db var encKey models.EncryptionKey @@ -992,6 +993,7 @@ func (s *managerServerV2) RequestEncryptionKey(ctx context.Context, req *manager } return &managerv2.RequestEncryptionKeyResponse{ + Status: managerv2.EncryptionStatus_ENCRYPTION_ENABLED, EncryptionKey: encKey.Key, }, nil } From 67253ffd0a81da25c961976483d7a84762648008 Mon Sep 17 00:00:00 2001 From: chohee Date: Wed, 24 Sep 2025 23:49:18 +0800 Subject: [PATCH 6/7] chore: clean up Signed-off-by: chohee --- cmd/manager/cmd/root.go | 8 -------- cmd/scheduler/cmd/root.go | 4 ---- go.mod | 3 --- go.sum | 2 ++ manager/config/config.go | 11 +++-------- manager/manager.go | 2 +- scheduler/config/config.go | 3 --- 7 files changed, 6 insertions(+), 27 deletions(-) diff --git a/cmd/manager/cmd/root.go b/cmd/manager/cmd/root.go index c804af35638..224f97719eb 100644 --- a/cmd/manager/cmd/root.go +++ b/cmd/manager/cmd/root.go @@ -101,10 +101,6 @@ func init() { // initDfpath initializes dfpath. func initDfpath(cfg *config.ServerConfig) (dfpath.Dfpath, error) { var options []dfpath.Option - if cfg.WorkHome != "" { - options = append(options, dfpath.WithWorkHome(cfg.WorkHome)) - } - if cfg.LogDir != "" { options = append(options, dfpath.WithLogDir(cfg.LogDir)) } @@ -117,10 +113,6 @@ func initDfpath(cfg *config.ServerConfig) (dfpath.Dfpath, error) { options = append(options, dfpath.WithPluginDir(cfg.PluginDir)) } - if cfg.DataDir != "" { - options = append(options, dfpath.WithDataDir(cfg.DataDir)) - } - return dfpath.New(options...) } diff --git a/cmd/scheduler/cmd/root.go b/cmd/scheduler/cmd/root.go index 8ca839cc9cf..6a5c1629d3a 100644 --- a/cmd/scheduler/cmd/root.go +++ b/cmd/scheduler/cmd/root.go @@ -100,10 +100,6 @@ func init() { func initDfpath(cfg *config.ServerConfig) (dfpath.Dfpath, error) { var options []dfpath.Option - if cfg.WorkHome != "" { - options = append(options, dfpath.WithWorkHome(cfg.WorkHome)) - } - if cfg.LogDir != "" { options = append(options, dfpath.WithLogDir(cfg.LogDir)) } diff --git a/go.mod b/go.mod index cf41472baeb..a5db61668ea 100644 --- a/go.mod +++ b/go.mod @@ -280,6 +280,3 @@ require ( gorm.io/driver/sqlserver v1.4.1 // indirect gorm.io/plugin/dbresolver v1.3.0 // indirect ) - -// use local api repo -replace d7y.io/api/v2 => ../api_fork diff --git a/go.sum b/go.sum index d15d33a1ef1..73ea59b9b92 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6Q cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= +d7y.io/api/v2 v2.1.60 h1:Imk1mw30wmTJNdqKFF5VO/eqI/t1H5PfIPPK4ryKX2U= +d7y.io/api/v2 v2.1.60/go.mod h1:WzakPywEgs27gr/TwrlRarPRRK2o5nN0YrTzHDdFii8= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= diff --git a/manager/config/config.go b/manager/config/config.go index f5ad7ec749e..2efeb62171e 100644 --- a/manager/config/config.go +++ b/manager/config/config.go @@ -67,9 +67,6 @@ type ServerConfig struct { // Server name. Name string `yaml:"name" mapstructure:"name"` - // Server work home directory. - WorkHome string `yaml:"workHome" mapstructure:"workHome"` - // Server dynamic config cache directory. CacheDir string `yaml:"cacheDir" mapstructure:"cacheDir"` @@ -91,9 +88,6 @@ type ServerConfig struct { // Server plugin directory. PluginDir string `yaml:"pluginDir" mapstructure:"pluginDir"` - // Server data directory. - DataDir string `yaml:"dataDir" mapstructure:"dataDir"` - // GRPC server configuration. GRPC GRPCConfig `yaml:"grpc" mapstructure:"grpc"` @@ -409,8 +403,10 @@ type NetworkConfig struct { EnableIPv6 bool `mapstructure:"enableIPv6" yaml:"enableIPv6"` } +// AES256 base64 key is 32 bytes. type EncryptionKey [32]byte type EncryptionConfig struct { + // Enable encryption. Enable bool `mapstructure:"enable" yaml:"enable"` // AES256 base64, optional Key *EncryptionKey `mapstructure:"key" yaml:"key"` @@ -418,8 +414,7 @@ type EncryptionConfig struct { // UnmarshalText Base64 func (e *EncryptionKey) UnmarshalText(text []byte) error { - // TODO: avoid printing key - logger.Infof("base64 key str: %s", string(text)) + logger.Debugf("base64 key str: %s", string(text)) keyBytes, err := base64.StdEncoding.DecodeString(string(text)) if err != nil { return fmt.Errorf("invalid base64 key: %v", err) diff --git a/manager/manager.go b/manager/manager.go index 5252dfbcd46..73ae120a82d 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -265,7 +265,7 @@ func registerGCTasks(gc pkggc.GC, db *gorm.DB) error { return nil } -// initializeEncryptionKey +// Initialize encryption key func initializeEncryptionKey(cfg *config.Config, db *gorm.DB) error { // 1. try get key from db var existingKey models.EncryptionKey diff --git a/scheduler/config/config.go b/scheduler/config/config.go index 0942af592fb..87a84e40957 100644 --- a/scheduler/config/config.go +++ b/scheduler/config/config.go @@ -85,9 +85,6 @@ type ServerConfig struct { // TLS server configuration. TLS *GRPCTLSServerConfig `yaml:"tls" mapstructure:"tls"` - // Server work home directory. - WorkHome string `yaml:"workHome" mapstructure:"workHome"` - // Server dynamic config cache directory. CacheDir string `yaml:"cacheDir" mapstructure:"cacheDir"` From a3e957817209049968207ff50c82ddccf5068acc Mon Sep 17 00:00:00 2001 From: chohee Date: Thu, 25 Sep 2025 00:11:39 +0800 Subject: [PATCH 7/7] feat: validate key Signed-off-by: chohee --- manager/config/config.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/manager/config/config.go b/manager/config/config.go index 2efeb62171e..b15fbb7a341 100644 --- a/manager/config/config.go +++ b/manager/config/config.go @@ -724,7 +724,11 @@ func (cfg *Config) Validate() error { } } - // TODO: validate key + if cfg.Encryption.Enable { + if cfg.Encryption.Key == nil { + return errors.New("encryption requires parameter key") + } + } return nil }