mirror of
https://github.com/cloudreve/cloudreve.git
synced 2025-12-15 10:04:01 +08:00
feat(encryption): add UI and settings for file encryption
This commit is contained in:
@@ -131,9 +131,9 @@ type Dep interface {
|
||||
// UAParser Get a singleton uaparser.Parser instance for user agent parsing.
|
||||
UAParser() *uaparser.Parser
|
||||
// MasterEncryptKeyVault Get a singleton encrypt.MasterEncryptKeyVault instance for master encrypt key vault.
|
||||
MasterEncryptKeyVault() encrypt.MasterEncryptKeyVault
|
||||
MasterEncryptKeyVault(ctx context.Context) encrypt.MasterEncryptKeyVault
|
||||
// EncryptorFactory Get a new encrypt.CryptorFactory instance.
|
||||
EncryptorFactory() encrypt.CryptorFactory
|
||||
EncryptorFactory(ctx context.Context) encrypt.CryptorFactory
|
||||
}
|
||||
|
||||
type dependency struct {
|
||||
@@ -183,7 +183,6 @@ type dependency struct {
|
||||
configPath string
|
||||
isPro bool
|
||||
requiredDbVersion string
|
||||
licenseKey string
|
||||
|
||||
// Protects inner deps that can be reloaded at runtime.
|
||||
mu sync.Mutex
|
||||
@@ -212,17 +211,17 @@ func (d *dependency) RequestClient(opts ...request.Option) request.Client {
|
||||
return request.NewClient(d.ConfigProvider(), opts...)
|
||||
}
|
||||
|
||||
func (d *dependency) MasterEncryptKeyVault() encrypt.MasterEncryptKeyVault {
|
||||
func (d *dependency) MasterEncryptKeyVault(ctx context.Context) encrypt.MasterEncryptKeyVault {
|
||||
if d.masterEncryptKeyVault != nil {
|
||||
return d.masterEncryptKeyVault
|
||||
}
|
||||
|
||||
d.masterEncryptKeyVault = encrypt.NewMasterEncryptKeyVault(d.SettingProvider())
|
||||
d.masterEncryptKeyVault = encrypt.NewMasterEncryptKeyVault(ctx, d.SettingProvider())
|
||||
return d.masterEncryptKeyVault
|
||||
}
|
||||
|
||||
func (d *dependency) EncryptorFactory() encrypt.CryptorFactory {
|
||||
return encrypt.NewCryptorFactory(d.MasterEncryptKeyVault())
|
||||
func (d *dependency) EncryptorFactory(ctx context.Context) encrypt.CryptorFactory {
|
||||
return encrypt.NewCryptorFactory(d.MasterEncryptKeyVault(ctx))
|
||||
}
|
||||
|
||||
func (d *dependency) WebAuthn(ctx context.Context) (*webauthn.WebAuthn, error) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package dependency
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/auth"
|
||||
@@ -11,7 +13,6 @@ import (
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||
"github.com/gin-contrib/static"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
// Option 发送请求的额外设置
|
||||
@@ -67,12 +68,6 @@ func WithProFlag(c bool) Option {
|
||||
})
|
||||
}
|
||||
|
||||
func WithLicenseKey(c string) Option {
|
||||
return optionFunc(func(o *dependency) {
|
||||
o.licenseKey = c
|
||||
})
|
||||
}
|
||||
|
||||
// WithRawEntClient Set the default raw ent client.
|
||||
func WithRawEntClient(c *ent.Client) Option {
|
||||
return optionFunc(func(o *dependency) {
|
||||
|
||||
2
assets
2
assets
Submodule assets updated: 1c9dd8d9ad...8b91fca929
230
cmd/masterkey.go
Normal file
230
cmd/masterkey.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/application/dependency"
|
||||
"github.com/cloudreve/Cloudreve/v4/ent/entity"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/encrypt"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
outputToFile string
|
||||
newMasterKeyFile string
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(masterKeyCmd)
|
||||
masterKeyCmd.AddCommand(masterKeyGenerateCmd)
|
||||
masterKeyCmd.AddCommand(masterKeyGetCmd)
|
||||
masterKeyCmd.AddCommand(masterKeyRotateCmd)
|
||||
|
||||
masterKeyGenerateCmd.Flags().StringVarP(&outputToFile, "output", "o", "", "Output master key to file instead of stdout")
|
||||
masterKeyRotateCmd.Flags().StringVarP(&newMasterKeyFile, "new-key", "n", "", "Path to file containing the new master key (base64 encoded).")
|
||||
}
|
||||
|
||||
var masterKeyCmd = &cobra.Command{
|
||||
Use: "master-key",
|
||||
Short: "Master encryption key management",
|
||||
Long: "Manage master encryption keys for file encryption. Use subcommands to generate, get, or rotate keys.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
_ = cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
var masterKeyGenerateCmd = &cobra.Command{
|
||||
Use: "generate",
|
||||
Short: "Generate a new master encryption key",
|
||||
Long: "Generate a new random 32-byte (256-bit) master encryption key and output it in base64 format.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Generate 32-byte random key
|
||||
key := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: Failed to generate random key: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Encode to base64
|
||||
encodedKey := base64.StdEncoding.EncodeToString(key)
|
||||
|
||||
if outputToFile != "" {
|
||||
// Write to file
|
||||
if err := os.WriteFile(outputToFile, []byte(encodedKey), 0600); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: Failed to write key to file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Master key generated and saved to: %s\n", outputToFile)
|
||||
} else {
|
||||
// Output to stdout
|
||||
fmt.Println(encodedKey)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var masterKeyGetCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get the current master encryption key",
|
||||
Long: "Retrieve and display the current master encryption key from the configured vault (setting, env, or file).",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
dep := dependency.NewDependency(
|
||||
dependency.WithConfigPath(confPath),
|
||||
)
|
||||
logger := dep.Logger()
|
||||
|
||||
// Get the master key vault
|
||||
vault := encrypt.NewMasterEncryptKeyVault(ctx, dep.SettingProvider())
|
||||
|
||||
// Retrieve the master key
|
||||
key, err := vault.GetMasterKey(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get master key: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Encode to base64 and display
|
||||
encodedKey := base64.StdEncoding.EncodeToString(key)
|
||||
fmt.Println("")
|
||||
fmt.Println(encodedKey)
|
||||
},
|
||||
}
|
||||
|
||||
var masterKeyRotateCmd = &cobra.Command{
|
||||
Use: "rotate",
|
||||
Short: "Rotate the master encryption key",
|
||||
Long: `Rotate the master encryption key by re-encrypting all encrypted file keys with a new master key.
|
||||
This operation:
|
||||
1. Retrieves the current master key
|
||||
2. Loads a new master key from file
|
||||
3. Re-encrypts all file encryption keys with the new master key
|
||||
4. Updates the master key in the settings database
|
||||
|
||||
Warning: This is a critical operation. Make sure to backup your database before proceeding.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
dep := dependency.NewDependency(
|
||||
dependency.WithConfigPath(confPath),
|
||||
)
|
||||
logger := dep.Logger()
|
||||
|
||||
logger.Info("Starting master key rotation...")
|
||||
|
||||
// Get the old master key
|
||||
vault := encrypt.NewMasterEncryptKeyVault(ctx, dep.SettingProvider())
|
||||
oldMasterKey, err := vault.GetMasterKey(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get current master key: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Info("Retrieved current master key")
|
||||
|
||||
// Get or generate the new master key
|
||||
var newMasterKey []byte
|
||||
// Load from file
|
||||
keyData, err := os.ReadFile(newMasterKeyFile)
|
||||
if err != nil {
|
||||
logger.Error("Failed to read new master key file: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
newMasterKey, err = base64.StdEncoding.DecodeString(string(keyData))
|
||||
if err != nil {
|
||||
logger.Error("Failed to decode new master key: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(newMasterKey) != 32 {
|
||||
logger.Error("Invalid new master key: must be 32 bytes (256 bits), got %d bytes", len(newMasterKey))
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Info("Loaded new master key from file: %s", newMasterKeyFile)
|
||||
|
||||
// Query all entities with encryption metadata
|
||||
db := dep.DBClient()
|
||||
entities, err := db.Entity.Query().
|
||||
Where(entity.Not(entity.PropsIsNil())).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Failed to query entities: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.Info("Found %d entities to check for encryption", len(entities))
|
||||
|
||||
// Re-encrypt each entity's encryption key
|
||||
encryptedCount := 0
|
||||
for _, ent := range entities {
|
||||
if ent.Props == nil || ent.Props.EncryptMetadata == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
encMeta := ent.Props.EncryptMetadata
|
||||
|
||||
// Decrypt the file key with old master key
|
||||
decryptedFileKey, err := encrypt.DecryptWithMasterKey(oldMasterKey, encMeta.Key)
|
||||
if err != nil {
|
||||
logger.Error("Failed to decrypt key for entity %d: %s", ent.ID, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Re-encrypt the file key with new master key
|
||||
newEncryptedKey, err := encrypt.EncryptWithMasterKey(newMasterKey, decryptedFileKey)
|
||||
if err != nil {
|
||||
logger.Error("Failed to re-encrypt key for entity %d: %s", ent.ID, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Update the entity
|
||||
newProps := *ent.Props
|
||||
newProps.EncryptMetadata = &types.EncryptMetadata{
|
||||
Algorithm: encMeta.Algorithm,
|
||||
Key: newEncryptedKey,
|
||||
KeyPlainText: nil, // Don't store plaintext
|
||||
IV: encMeta.IV,
|
||||
}
|
||||
|
||||
err = db.Entity.UpdateOne(ent).
|
||||
SetProps(&newProps).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Failed to update entity %d: %s", ent.ID, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
encryptedCount++
|
||||
}
|
||||
|
||||
logger.Info("Re-encrypted %d file keys", encryptedCount)
|
||||
|
||||
// Update the master key in settings
|
||||
keyStore := dep.SettingProvider().MasterEncryptKeyVault(ctx)
|
||||
if keyStore == setting.MasterEncryptKeyVaultTypeSetting {
|
||||
encodedNewKey := base64.StdEncoding.EncodeToString(newMasterKey)
|
||||
err = dep.SettingClient().Set(ctx, map[string]string{
|
||||
"encrypt_master_key": encodedNewKey,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Failed to update master key in settings: %s", err)
|
||||
logger.Error("WARNING: File keys have been re-encrypted but master key update failed!")
|
||||
logger.Error("Please manually update the encrypt_master_key setting.")
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
logger.Info("Current master key is stored in %q", keyStore)
|
||||
if keyStore == setting.MasterEncryptKeyVaultTypeEnv {
|
||||
logger.Info("Please update the new master encryption key in your \"CR_ENCRYPT_MASTER_KEY\" environment variable.")
|
||||
} else if keyStore == setting.MasterEncryptKeyVaultTypeFile {
|
||||
logger.Info("Please update the new master encryption key in your key file: %q", dep.SettingProvider().MasterEncryptKeyFile(ctx))
|
||||
}
|
||||
logger.Info("Last step: Please manually update the new master encryption key in your ENV or key file.")
|
||||
}
|
||||
|
||||
logger.Info("Master key rotation completed successfully")
|
||||
},
|
||||
}
|
||||
@@ -2,14 +2,16 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
confPath string
|
||||
confPath string
|
||||
licenseKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -12,10 +12,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
licenseKey string
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(serverCmd)
|
||||
serverCmd.PersistentFlags().StringVarP(&licenseKey, "license-key", "l", "", "License key of your Cloudreve Pro")
|
||||
@@ -29,7 +25,6 @@ var serverCmd = &cobra.Command{
|
||||
dependency.WithConfigPath(confPath),
|
||||
dependency.WithProFlag(constants.IsProBool),
|
||||
dependency.WithRequiredDbVersion(constants.BackendVersion),
|
||||
dependency.WithLicenseKey(licenseKey),
|
||||
)
|
||||
server := application.NewServer(dep)
|
||||
logger := dep.Logger()
|
||||
|
||||
@@ -665,6 +665,14 @@ var DefaultSettings = map[string]string{
|
||||
"headless_bottom_html": "",
|
||||
"sidebar_bottom_html": "",
|
||||
"encrypt_master_key": "",
|
||||
"encrypt_master_key_vault": "setting",
|
||||
"encrypt_master_key_file": "",
|
||||
"show_encryption_status": "1",
|
||||
}
|
||||
|
||||
var RedactedSettings = map[string]struct{}{
|
||||
"encrypt_master_key": {},
|
||||
"secret_key": {},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -161,13 +161,13 @@ type (
|
||||
EncryptMetadata *EncryptMetadata `json:"encrypt_metadata,omitempty"`
|
||||
}
|
||||
|
||||
Algorithm string
|
||||
Cipher string
|
||||
|
||||
EncryptMetadata struct {
|
||||
Algorithm Algorithm `json:"algorithm"`
|
||||
Key []byte `json:"key"`
|
||||
KeyPlainText []byte `json:"key_plain_text,omitempty"`
|
||||
IV []byte `json:"iv"`
|
||||
Algorithm Cipher `json:"algorithm"`
|
||||
Key []byte `json:"key"`
|
||||
KeyPlainText []byte `json:"key_plain_text,omitempty"`
|
||||
IV []byte `json:"iv"`
|
||||
}
|
||||
|
||||
DavAccountProps struct {
|
||||
@@ -361,5 +361,5 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
AlgorithmAES256CTR Algorithm = "aes-256-ctr"
|
||||
CipherAES256CTR Cipher = "aes-256-ctr"
|
||||
)
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
// Using the factory pattern:
|
||||
//
|
||||
// factory := NewDecrypterFactory(masterKeyVault)
|
||||
// decrypter, err := factory(types.AlgorithmAES256CTR)
|
||||
// decrypter, err := factory(types.CipherAES256CTR)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
@@ -131,7 +131,7 @@ func (e *AES256CTR) GenerateMetadata(ctx context.Context) (*types.EncryptMetadat
|
||||
}
|
||||
|
||||
return &types.EncryptMetadata{
|
||||
Algorithm: types.AlgorithmAES256CTR,
|
||||
Algorithm: types.CipherAES256CTR,
|
||||
Key: encryptedKey,
|
||||
KeyPlainText: key,
|
||||
IV: iv,
|
||||
@@ -144,7 +144,7 @@ func (e *AES256CTR) LoadMetadata(ctx context.Context, encryptedMetadata *types.E
|
||||
return fmt.Errorf("encryption metadata is nil")
|
||||
}
|
||||
|
||||
if encryptedMetadata.Algorithm != types.AlgorithmAES256CTR {
|
||||
if encryptedMetadata.Algorithm != types.CipherAES256CTR {
|
||||
return fmt.Errorf("unsupported algorithm: %s", encryptedMetadata.Algorithm)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,13 +23,13 @@ type (
|
||||
GenerateMetadata(ctx context.Context) (*types.EncryptMetadata, error)
|
||||
}
|
||||
|
||||
CryptorFactory func(algorithm types.Algorithm) (Cryptor, error)
|
||||
CryptorFactory func(algorithm types.Cipher) (Cryptor, error)
|
||||
)
|
||||
|
||||
func NewCryptorFactory(masterKeyVault MasterEncryptKeyVault) CryptorFactory {
|
||||
return func(algorithm types.Algorithm) (Cryptor, error) {
|
||||
return func(algorithm types.Cipher) (Cryptor, error) {
|
||||
switch algorithm {
|
||||
case types.AlgorithmAES256CTR:
|
||||
case types.CipherAES256CTR:
|
||||
return NewAES256CTR(masterKeyVault), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown algorithm: %s", algorithm)
|
||||
|
||||
@@ -2,18 +2,33 @@ package encrypt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
EnvMasterEncryptKey = "CR_ENCRYPT_MASTER_KEY"
|
||||
)
|
||||
|
||||
// MasterEncryptKeyVault is a vault for the master encrypt key.
|
||||
type MasterEncryptKeyVault interface {
|
||||
GetMasterKey(ctx context.Context) ([]byte, error)
|
||||
}
|
||||
|
||||
func NewMasterEncryptKeyVault(setting setting.Provider) MasterEncryptKeyVault {
|
||||
return &settingMasterEncryptKeyVault{setting: setting}
|
||||
func NewMasterEncryptKeyVault(ctx context.Context, settings setting.Provider) MasterEncryptKeyVault {
|
||||
vaultType := settings.MasterEncryptKeyVault(ctx)
|
||||
switch vaultType {
|
||||
case setting.MasterEncryptKeyVaultTypeEnv:
|
||||
return NewEnvMasterEncryptKeyVault()
|
||||
case setting.MasterEncryptKeyVaultTypeFile:
|
||||
return NewFileMasterEncryptKeyVault(settings.MasterEncryptKeyFile(ctx))
|
||||
default:
|
||||
return NewSettingMasterEncryptKeyVault(settings)
|
||||
}
|
||||
}
|
||||
|
||||
// settingMasterEncryptKeyVault is a vault for the master encrypt key that gets the key from the setting KV.
|
||||
@@ -21,6 +36,10 @@ type settingMasterEncryptKeyVault struct {
|
||||
setting setting.Provider
|
||||
}
|
||||
|
||||
func NewSettingMasterEncryptKeyVault(setting setting.Provider) MasterEncryptKeyVault {
|
||||
return &settingMasterEncryptKeyVault{setting: setting}
|
||||
}
|
||||
|
||||
func (v *settingMasterEncryptKeyVault) GetMasterKey(ctx context.Context) ([]byte, error) {
|
||||
key := v.setting.MasterEncryptKey(ctx)
|
||||
if key == nil {
|
||||
@@ -28,3 +47,59 @@ func (v *settingMasterEncryptKeyVault) GetMasterKey(ctx context.Context) ([]byte
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func NewEnvMasterEncryptKeyVault() MasterEncryptKeyVault {
|
||||
return &envMasterEncryptKeyVault{}
|
||||
}
|
||||
|
||||
type envMasterEncryptKeyVault struct {
|
||||
}
|
||||
|
||||
var envMasterKeyCache = []byte{}
|
||||
|
||||
func (v *envMasterEncryptKeyVault) GetMasterKey(ctx context.Context) ([]byte, error) {
|
||||
if len(envMasterKeyCache) > 0 {
|
||||
return envMasterKeyCache, nil
|
||||
}
|
||||
|
||||
key := os.Getenv(EnvMasterEncryptKey)
|
||||
if key == "" {
|
||||
return nil, errors.New("master encrypt key is not set")
|
||||
}
|
||||
|
||||
decodedKey, err := base64.StdEncoding.DecodeString(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode master encrypt key: %w", err)
|
||||
}
|
||||
|
||||
envMasterKeyCache = decodedKey
|
||||
return decodedKey, nil
|
||||
}
|
||||
|
||||
func NewFileMasterEncryptKeyVault(path string) MasterEncryptKeyVault {
|
||||
return &fileMasterEncryptKeyVault{path: path}
|
||||
}
|
||||
|
||||
var fileMasterKeyCache = []byte{}
|
||||
|
||||
type fileMasterEncryptKeyVault struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (v *fileMasterEncryptKeyVault) GetMasterKey(ctx context.Context) ([]byte, error) {
|
||||
if len(fileMasterKeyCache) > 0 {
|
||||
return fileMasterKeyCache, nil
|
||||
}
|
||||
|
||||
key, err := os.ReadFile(v.path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid master encrypt key file")
|
||||
}
|
||||
|
||||
decodedKey, err := base64.StdEncoding.DecodeString(string(key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid master encrypt key")
|
||||
}
|
||||
fileMasterKeyCache = decodedKey
|
||||
return fileMasterKeyCache, nil
|
||||
}
|
||||
|
||||
@@ -652,8 +652,8 @@ func (f *DBFS) createFile(ctx context.Context, parent *File, name string, fileTy
|
||||
|
||||
func (f *DBFS) generateEncryptMetadata(ctx context.Context, uploadRequest *fs.UploadRequest, policy *ent.StoragePolicy) (*types.EncryptMetadata, error) {
|
||||
relayEnabled := policy.Settings != nil && policy.Settings.Relay
|
||||
if (len(uploadRequest.Props.EncryptionSupported) > 0 && uploadRequest.Props.EncryptionSupported[0] == types.AlgorithmAES256CTR) || relayEnabled {
|
||||
encryptor, err := f.encryptorFactory(types.AlgorithmAES256CTR)
|
||||
if (len(uploadRequest.Props.EncryptionSupported) > 0 && uploadRequest.Props.EncryptionSupported[0] == types.CipherAES256CTR) || relayEnabled {
|
||||
encryptor, err := f.encryptorFactory(types.CipherAES256CTR)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get encryptor: %w", err)
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ type (
|
||||
// with a default version entity. This will be set in update request for existing files.
|
||||
EntityType *types.EntityType
|
||||
ExpireAt time.Time
|
||||
EncryptionSupported []types.Algorithm
|
||||
EncryptionSupported []types.Cipher
|
||||
ClientSideEncrypted bool // Whether the file stream is already encrypted by client side.
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ func (m *manager) GetDirectLink(ctx context.Context, urls ...*fs.URI) ([]DirectL
|
||||
}
|
||||
|
||||
source := entitysource.NewEntitySource(target, d, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(),
|
||||
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory())
|
||||
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory(ctx))
|
||||
sourceUrl, err := source.Url(ctx,
|
||||
entitysource.WithSpeedLimit(int64(m.user.Edges.Group.SpeedLimit)),
|
||||
entitysource.WithDisplayName(file.Name()),
|
||||
@@ -182,7 +182,7 @@ func (m *manager) GetUrlForRedirectedDirectLink(ctx context.Context, dl *ent.Dir
|
||||
}
|
||||
|
||||
source := entitysource.NewEntitySource(primaryEntity, d, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(),
|
||||
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory())
|
||||
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory(ctx))
|
||||
downloadUrl, err := source.Url(ctx,
|
||||
entitysource.WithExpire(o.Expire),
|
||||
entitysource.WithDownload(o.IsDownload),
|
||||
@@ -282,7 +282,7 @@ func (m *manager) GetEntityUrls(ctx context.Context, args []GetEntityUrlArgs, op
|
||||
|
||||
// Cache miss, Generate new url
|
||||
source := entitysource.NewEntitySource(target, d, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(),
|
||||
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory())
|
||||
m.l, m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory(ctx))
|
||||
downloadUrl, err := source.Url(ctx,
|
||||
entitysource.WithExpire(o.Expire),
|
||||
entitysource.WithDownload(o.IsDownload),
|
||||
@@ -349,7 +349,7 @@ func (m *manager) GetEntitySource(ctx context.Context, entityID int, opts ...fs.
|
||||
}
|
||||
|
||||
return entitysource.NewEntitySource(entity, handler, policy, m.auth, m.settings, m.hasher, m.dep.RequestClient(), m.l,
|
||||
m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory(), entitysource.WithContext(ctx), entitysource.WithThumb(o.IsThumb)), nil
|
||||
m.config, m.dep.MimeDetector(ctx), m.dep.EncryptorFactory(ctx), entitysource.WithContext(ctx), entitysource.WithThumb(o.IsThumb)), nil
|
||||
}
|
||||
|
||||
func (l *manager) SetCurrentVersion(ctx context.Context, path *fs.URI, version int) error {
|
||||
|
||||
@@ -148,7 +148,7 @@ func NewFileManager(dep dependency.Dep, u *ent.User) FileManager {
|
||||
settings: dep.SettingProvider(),
|
||||
fs: dbfs.NewDatabaseFS(u, dep.FileClient(), dep.ShareClient(), dep.Logger(), dep.LockSystem(),
|
||||
dep.SettingProvider(), dep.StoragePolicyClient(), dep.HashIDEncoder(), dep.UserClient(), dep.KV(), dep.NavigatorStateKV(),
|
||||
dep.DirectLinkClient(), dep.EncryptorFactory()),
|
||||
dep.DirectLinkClient(), dep.EncryptorFactory(context.TODO())),
|
||||
kv: dep.KV(),
|
||||
config: config,
|
||||
auth: dep.GeneralAuth(),
|
||||
|
||||
@@ -64,7 +64,8 @@ func (m *manager) Thumbnail(ctx context.Context, uri *fs.URI) (entitysource.Enti
|
||||
capabilities := handler.Capabilities()
|
||||
// Check if file extension and size is supported by native policy generator.
|
||||
if capabilities.ThumbSupportAllExts || util.IsInExtensionList(capabilities.ThumbSupportedExts, file.DisplayName()) &&
|
||||
(capabilities.ThumbMaxSize == 0 || latest.Size() <= capabilities.ThumbMaxSize) {
|
||||
(capabilities.ThumbMaxSize == 0 || latest.Size() <= capabilities.ThumbMaxSize) &&
|
||||
!latest.Encrypted() {
|
||||
thumbSource, err := m.GetEntitySource(ctx, 0, fs.WithEntity(latest), fs.WithUseThumb(true))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get latest entity source: %w", err)
|
||||
|
||||
@@ -192,7 +192,7 @@ func (m *manager) Upload(ctx context.Context, req *fs.UploadRequest, policy *ent
|
||||
}
|
||||
|
||||
if session != nil && session.EncryptMetadata != nil && !req.Props.ClientSideEncrypted {
|
||||
cryptor, err := m.dep.EncryptorFactory()(session.EncryptMetadata.Algorithm)
|
||||
cryptor, err := m.dep.EncryptorFactory(ctx)(session.EncryptMetadata.Algorithm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create cryptor: %w", err)
|
||||
}
|
||||
@@ -331,7 +331,7 @@ func (m *manager) Update(ctx context.Context, req *fs.UploadRequest, opts ...fs.
|
||||
|
||||
req.Props.UploadSessionID = uuid.Must(uuid.NewV4()).String()
|
||||
// Sever side supported encryption algorithms
|
||||
req.Props.EncryptionSupported = []types.Algorithm{types.AlgorithmAES256CTR}
|
||||
req.Props.EncryptionSupported = []types.Cipher{types.CipherAES256CTR}
|
||||
|
||||
if m.stateless {
|
||||
return m.updateStateless(ctx, req, o)
|
||||
|
||||
@@ -218,7 +218,7 @@ func (m *CreateArchiveTask) listEntitiesAndSendToSlave(ctx context.Context, dep
|
||||
user := inventory.UserFromContext(ctx)
|
||||
fm := manager.NewFileManager(dep, user)
|
||||
storagePolicyClient := dep.StoragePolicyClient()
|
||||
masterKey, _ := dep.MasterEncryptKeyVault().GetMasterKey(ctx)
|
||||
masterKey, _ := dep.MasterEncryptKeyVault(ctx).GetMasterKey(ctx)
|
||||
|
||||
failed, err := fm.CreateArchive(ctx, uris, io.Discard,
|
||||
fs.WithDryRun(func(name string, e fs.Entity) {
|
||||
|
||||
@@ -194,7 +194,7 @@ func (m *ExtractArchiveTask) createSlaveExtractTask(ctx context.Context, dep dep
|
||||
return task.StatusError, fmt.Errorf("failed to get policy: %w", err)
|
||||
}
|
||||
|
||||
masterKey, _ := dep.MasterEncryptKeyVault().GetMasterKey(ctx)
|
||||
masterKey, _ := dep.MasterEncryptKeyVault(ctx).GetMasterKey(ctx)
|
||||
entityModel, err := decryptEntityKeyIfNeeded(masterKey, archiveFile.PrimaryEntity().Model())
|
||||
if err != nil {
|
||||
return task.StatusError, fmt.Errorf("failed to decrypt entity key for archive file %q: %s", archiveFile.DisplayName(), err)
|
||||
|
||||
@@ -210,6 +210,12 @@ type (
|
||||
FFMpegExtraArgs(ctx context.Context) string
|
||||
// MasterEncryptKey returns the master encrypt key.
|
||||
MasterEncryptKey(ctx context.Context) []byte
|
||||
// MasterEncryptKeyVault returns the master encrypt key vault type.
|
||||
MasterEncryptKeyVault(ctx context.Context) MasterEncryptKeyVaultType
|
||||
// MasterEncryptKeyFile returns the master encrypt key file.
|
||||
MasterEncryptKeyFile(ctx context.Context) string
|
||||
// ShowEncryptionStatus returns true if encryption status is shown.
|
||||
ShowEncryptionStatus(ctx context.Context) bool
|
||||
}
|
||||
UseFirstSiteUrlCtxKey = struct{}
|
||||
)
|
||||
@@ -237,6 +243,18 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func (s *settingProvider) ShowEncryptionStatus(ctx context.Context) bool {
|
||||
return s.getBoolean(ctx, "show_encryption_status", true)
|
||||
}
|
||||
|
||||
func (s *settingProvider) MasterEncryptKeyFile(ctx context.Context) string {
|
||||
return s.getString(ctx, "encrypt_master_key_file", "")
|
||||
}
|
||||
|
||||
func (s *settingProvider) MasterEncryptKeyVault(ctx context.Context) MasterEncryptKeyVaultType {
|
||||
return MasterEncryptKeyVaultType(s.getString(ctx, "encrypt_master_key_vault", "setting"))
|
||||
}
|
||||
|
||||
func (s *settingProvider) MasterEncryptKey(ctx context.Context) []byte {
|
||||
encoded := s.getString(ctx, "encrypt_master_key", "")
|
||||
key, err := base64.StdEncoding.DecodeString(encoded)
|
||||
|
||||
@@ -223,3 +223,11 @@ type CustomHTML struct {
|
||||
HeadlessBody string `json:"headless_bottom,omitempty"`
|
||||
SidebarBottom string `json:"sidebar_bottom,omitempty"`
|
||||
}
|
||||
|
||||
type MasterEncryptKeyVaultType string
|
||||
|
||||
const (
|
||||
MasterEncryptKeyVaultTypeSetting = MasterEncryptKeyVaultType("setting")
|
||||
MasterEncryptKeyVaultTypeEnv = MasterEncryptKeyVaultType("env")
|
||||
MasterEncryptKeyVaultTypeFile = MasterEncryptKeyVaultType("file")
|
||||
)
|
||||
|
||||
@@ -347,7 +347,7 @@ func (s *SingleFileService) Url(c *gin.Context) (string, error) {
|
||||
}
|
||||
|
||||
es := entitysource.NewEntitySource(fs.NewEntity(primaryEntity), driver, policy, dep.GeneralAuth(),
|
||||
dep.SettingProvider(), dep.HashIDEncoder(), dep.RequestClient(), dep.Logger(), dep.ConfigProvider(), dep.MimeDetector(ctx), dep.EncryptorFactory())
|
||||
dep.SettingProvider(), dep.HashIDEncoder(), dep.RequestClient(), dep.Logger(), dep.ConfigProvider(), dep.MimeDetector(ctx), dep.EncryptorFactory(ctx))
|
||||
|
||||
expire := time.Now().Add(time.Hour * 1)
|
||||
url, err := es.Url(ctx, entitysource.WithExpire(&expire), entitysource.WithDisplayName(file.Name))
|
||||
@@ -547,7 +547,7 @@ func (s *SingleEntityService) Url(c *gin.Context) (string, error) {
|
||||
}
|
||||
|
||||
es := entitysource.NewEntitySource(fs.NewEntity(entity), driver, policy, dep.GeneralAuth(),
|
||||
dep.SettingProvider(), dep.HashIDEncoder(), dep.RequestClient(), dep.Logger(), dep.ConfigProvider(), dep.MimeDetector(c), dep.EncryptorFactory())
|
||||
dep.SettingProvider(), dep.HashIDEncoder(), dep.RequestClient(), dep.Logger(), dep.ConfigProvider(), dep.MimeDetector(c), dep.EncryptorFactory(c))
|
||||
|
||||
expire := time.Now().Add(time.Hour * 1)
|
||||
url, err := es.Url(c, entitysource.WithDownload(true), entitysource.WithExpire(&expire), entitysource.WithDisplayName(path.Base(entity.Source)))
|
||||
|
||||
@@ -193,7 +193,8 @@ type (
|
||||
func (s *GetSettingService) GetSetting(c *gin.Context) (map[string]string, error) {
|
||||
dep := dependency.FromContext(c)
|
||||
res, err := dep.SettingClient().Gets(c, lo.Filter(s.Keys, func(item string, index int) bool {
|
||||
return item != "secret_key"
|
||||
_, ok := inventory.RedactedSettings[strings.ToLower(item)]
|
||||
return !ok
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, serializer.NewError(serializer.CodeDBError, "Failed to get settings", err)
|
||||
|
||||
@@ -43,16 +43,17 @@ type SiteConfig struct {
|
||||
PrivacyPolicyUrl string `json:"privacy_policy_url,omitempty"`
|
||||
|
||||
// Explorer section
|
||||
Icons string `json:"icons,omitempty"`
|
||||
EmojiPreset string `json:"emoji_preset,omitempty"`
|
||||
MapProvider setting.MapProvider `json:"map_provider,omitempty"`
|
||||
GoogleMapTileType setting.MapGoogleTileType `json:"google_map_tile_type,omitempty"`
|
||||
MapboxAK string `json:"mapbox_ak,omitempty"`
|
||||
FileViewers []types.ViewerGroup `json:"file_viewers,omitempty"`
|
||||
MaxBatchSize int `json:"max_batch_size,omitempty"`
|
||||
ThumbnailWidth int `json:"thumbnail_width,omitempty"`
|
||||
ThumbnailHeight int `json:"thumbnail_height,omitempty"`
|
||||
CustomProps []types.CustomProps `json:"custom_props,omitempty"`
|
||||
Icons string `json:"icons,omitempty"`
|
||||
EmojiPreset string `json:"emoji_preset,omitempty"`
|
||||
MapProvider setting.MapProvider `json:"map_provider,omitempty"`
|
||||
GoogleMapTileType setting.MapGoogleTileType `json:"google_map_tile_type,omitempty"`
|
||||
MapboxAK string `json:"mapbox_ak,omitempty"`
|
||||
FileViewers []types.ViewerGroup `json:"file_viewers,omitempty"`
|
||||
MaxBatchSize int `json:"max_batch_size,omitempty"`
|
||||
ThumbnailWidth int `json:"thumbnail_width,omitempty"`
|
||||
ThumbnailHeight int `json:"thumbnail_height,omitempty"`
|
||||
CustomProps []types.CustomProps `json:"custom_props,omitempty"`
|
||||
ShowEncryptionStatus bool `json:"show_encryption_status,omitempty"`
|
||||
|
||||
// Thumbnail section
|
||||
ThumbExts []string `json:"thumb_exts,omitempty"`
|
||||
@@ -100,6 +101,7 @@ func (s *GetSettingService) GetSiteConfig(c *gin.Context) (*SiteConfig, error) {
|
||||
fileViewers := settings.FileViewers(c)
|
||||
customProps := settings.CustomProps(c)
|
||||
maxBatchSize := settings.MaxBatchedFile(c)
|
||||
showEncryptionStatus := settings.ShowEncryptionStatus(c)
|
||||
w, h := settings.ThumbSize(c)
|
||||
for i := range fileViewers {
|
||||
for j := range fileViewers[i].Viewers {
|
||||
@@ -107,15 +109,16 @@ func (s *GetSettingService) GetSiteConfig(c *gin.Context) (*SiteConfig, error) {
|
||||
}
|
||||
}
|
||||
return &SiteConfig{
|
||||
MaxBatchSize: maxBatchSize,
|
||||
FileViewers: fileViewers,
|
||||
Icons: explorerSettings.Icons,
|
||||
MapProvider: mapSettings.Provider,
|
||||
GoogleMapTileType: mapSettings.GoogleTileType,
|
||||
MapboxAK: mapSettings.MapboxAK,
|
||||
ThumbnailWidth: w,
|
||||
ThumbnailHeight: h,
|
||||
CustomProps: customProps,
|
||||
MaxBatchSize: maxBatchSize,
|
||||
FileViewers: fileViewers,
|
||||
Icons: explorerSettings.Icons,
|
||||
MapProvider: mapSettings.Provider,
|
||||
GoogleMapTileType: mapSettings.GoogleTileType,
|
||||
MapboxAK: mapSettings.MapboxAK,
|
||||
ThumbnailWidth: w,
|
||||
ThumbnailHeight: h,
|
||||
CustomProps: customProps,
|
||||
ShowEncryptionStatus: showEncryptionStatus,
|
||||
}, nil
|
||||
case "emojis":
|
||||
emojis := settings.EmojiPresets(c)
|
||||
|
||||
@@ -292,6 +292,7 @@ type Entity struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
StoragePolicy *StoragePolicy `json:"storage_policy,omitempty"`
|
||||
CreatedBy *user.User `json:"created_by,omitempty"`
|
||||
EncryptedWith types.Cipher `json:"encrypted_with,omitempty"`
|
||||
}
|
||||
|
||||
type Share struct {
|
||||
@@ -452,6 +453,12 @@ func BuildEntity(extendedInfo *fs.FileExtendedInfo, e fs.Entity, hasher hashid.E
|
||||
userRedacted := user.BuildUserRedacted(e.CreatedBy(), user.RedactLevelAnonymous, hasher)
|
||||
u = &userRedacted
|
||||
}
|
||||
|
||||
encryptedWith := types.Cipher("")
|
||||
if e.Encrypted() {
|
||||
encryptedWith = e.Props().EncryptMetadata.Algorithm
|
||||
}
|
||||
|
||||
return Entity{
|
||||
ID: hashid.EncodeEntityID(hasher, e.ID()),
|
||||
Type: e.Type(),
|
||||
@@ -459,6 +466,7 @@ func BuildEntity(extendedInfo *fs.FileExtendedInfo, e fs.Entity, hasher hashid.E
|
||||
StoragePolicy: BuildStoragePolicy(extendedInfo.EntityStoragePolicies[e.PolicyID()], hasher),
|
||||
Size: e.Size(),
|
||||
CreatedBy: u,
|
||||
EncryptedWith: encryptedWith,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ type (
|
||||
PolicyID string `json:"policy_id"`
|
||||
Metadata map[string]string `json:"metadata" binding:"max=256"`
|
||||
EntityType string `json:"entity_type" binding:"eq=|eq=live_photo|eq=version"`
|
||||
EncryptionSupported []types.Algorithm `json:"encryption_supported"`
|
||||
EncryptionSupported []types.Cipher `json:"encryption_supported"`
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user