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/manager/config/config.go b/manager/config/config.go index 5576dda57c3..b15fbb7a341 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,6 +58,9 @@ type Config struct { // Network configuration. Network NetworkConfig `yaml:"network" mapstructure:"network"` + + // Encryption configuration + Encryption EncryptionConfig `yaml:"encryption" mapstructure:"encryption"` } type ServerConfig struct { @@ -398,6 +403,36 @@ 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"` +} + +// UnmarshalText Base64 +func (e *EncryptionKey) UnmarshalText(text []byte) error { + 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) + } + + 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 +524,9 @@ func New() *Config { Network: NetworkConfig{ EnableIPv6: DefaultNetworkEnableIPv6, }, + Encryption: EncryptionConfig{ + Enable: false, + }, } } @@ -686,6 +724,12 @@ func (cfg *Config) Validate() error { } } + if cfg.Encryption.Enable { + if cfg.Encryption.Key == nil { + return errors.New("encryption requires parameter 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..73ae120a82d 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" @@ -37,6 +41,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" @@ -122,6 +127,16 @@ func New(cfg *config.Config, d dfpath.Dfpath) (*Server, error) { return nil, err } + // Initialize encryption key + if cfg.Encryption.Enable { + if err := initializeEncryptionKey(cfg, db.DB); err != nil { + return nil, err + } + logger.Infof("encryption enabled") + } else { + logger.Infof("encryption disabled") + } + // Initialize enforcer. enforcer, err := rbac.NewEnforcer(db.DB) if err != nil { @@ -250,6 +265,35 @@ func registerGCTasks(gc pkggc.GC, db *gorm.DB) error { return nil } +// Initialize encryption key +func initializeEncryptionKey(cfg *config.Config, db *gorm.DB) error { + // 1. try get key from db + var existingKey models.EncryptionKey + if err := db.First(&existingKey).Error; err == nil { + 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) + } + + // 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) + } + 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..98da1353e5b 100644 --- a/manager/rpcserver/manager_server_v2.go +++ b/manager/rpcserver/manager_server_v2.go @@ -976,3 +976,24 @@ 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 { + return &managerv2.RequestEncryptionKeyResponse{ + Status: managerv2.EncryptionStatus_ENCRYPTION_DISABLED, + }, nil + } + // 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{ + Status: managerv2.EncryptionStatus_ENCRYPTION_ENABLED, + EncryptionKey: encKey.Key, + }, nil +}