feat(encryption): add UI and settings for file encryption

This commit is contained in:
Aaron Liu
2025-10-24 15:04:54 +08:00
parent 16b02b1fb3
commit e3580d9351
26 changed files with 415 additions and 72 deletions

View File

@@ -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) {

View File

@@ -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

Submodule assets updated: 1c9dd8d9ad...8b91fca929

230
cmd/masterkey.go Normal file
View 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")
},
}

View File

@@ -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() {

View File

@@ -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()

View File

@@ -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() {

View File

@@ -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"
)

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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.
}

View File

@@ -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 {

View File

@@ -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(),

View File

@@ -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)

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")
)

View 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)))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,
}
}

View File

@@ -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"`
}
)