mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 15:10:23 +00:00
azblob: store SAS URL as kms.Secret
This commit is contained in:
parent
8607788975
commit
9d3d7db29c
26 changed files with 1026 additions and 72 deletions
|
@ -176,7 +176,7 @@ Please take a look at the usage below to customize the serving parameters`,
|
|||
AccountKey: kms.NewPlainSecret(portableAzAccountKey),
|
||||
Endpoint: portableAzEndpoint,
|
||||
AccessTier: portableAzAccessTier,
|
||||
SASURL: portableAzSASURL,
|
||||
SASURL: kms.NewPlainSecret(portableAzSASURL),
|
||||
KeyPrefix: portableAzKeyPrefix,
|
||||
UseEmulator: portableAzUseEmulator,
|
||||
UploadPartSize: int64(portableAzULPartSize),
|
||||
|
|
|
@ -117,9 +117,7 @@ func newActionNotification(
|
|||
bucket = fsConfig.GCSConfig.Bucket
|
||||
case vfs.AzureBlobFilesystemProvider:
|
||||
bucket = fsConfig.AzBlobConfig.Container
|
||||
if fsConfig.AzBlobConfig.SASURL != "" {
|
||||
endpoint = fsConfig.AzBlobConfig.SASURL
|
||||
} else {
|
||||
if fsConfig.AzBlobConfig.Endpoint != "" {
|
||||
endpoint = fsConfig.AzBlobConfig.Endpoint
|
||||
}
|
||||
case vfs.SFTPFilesystemProvider:
|
||||
|
|
|
@ -29,7 +29,6 @@ func TestNewActionNotification(t *testing.T) {
|
|||
}
|
||||
user.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{
|
||||
Container: "azcontainer",
|
||||
SASURL: "azsasurl",
|
||||
Endpoint: "azendpoint",
|
||||
}
|
||||
user.FsConfig.SFTPConfig = vfs.SFTPFsConfig{
|
||||
|
@ -56,10 +55,9 @@ func TestNewActionNotification(t *testing.T) {
|
|||
user.FsConfig.Provider = vfs.AzureBlobFilesystemProvider
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSCP, 123, 0, nil)
|
||||
assert.Equal(t, "azcontainer", a.Bucket)
|
||||
assert.Equal(t, "azsasurl", a.Endpoint)
|
||||
assert.Equal(t, "azendpoint", a.Endpoint)
|
||||
assert.Equal(t, 1, a.Status)
|
||||
|
||||
user.FsConfig.AzBlobConfig.SASURL = ""
|
||||
a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", ProtocolSCP, 123, os.O_APPEND, nil)
|
||||
assert.Equal(t, "azcontainer", a.Bucket)
|
||||
assert.Equal(t, "azendpoint", a.Endpoint)
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
boltDatabaseVersion = 6
|
||||
boltDatabaseVersion = 10
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -229,7 +229,7 @@ func (p *BoltProvider) adminExists(username string) (Admin, error) {
|
|||
var admin Admin
|
||||
|
||||
err := p.dbHandle.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := getAdminBucket(tx)
|
||||
bucket, err := getAdminsBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -249,7 +249,7 @@ func (p *BoltProvider) addAdmin(admin *Admin) error {
|
|||
return err
|
||||
}
|
||||
return p.dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getAdminBucket(tx)
|
||||
bucket, err := getAdminsBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -275,7 +275,7 @@ func (p *BoltProvider) updateAdmin(admin *Admin) error {
|
|||
return err
|
||||
}
|
||||
return p.dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getAdminBucket(tx)
|
||||
bucket, err := getAdminsBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ func (p *BoltProvider) updateAdmin(admin *Admin) error {
|
|||
|
||||
func (p *BoltProvider) deleteAdmin(admin *Admin) error {
|
||||
return p.dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getAdminBucket(tx)
|
||||
bucket, err := getAdminsBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -318,7 +318,7 @@ func (p *BoltProvider) getAdmins(limit int, offset int, order string) ([]Admin,
|
|||
admins := make([]Admin, 0, limit)
|
||||
|
||||
err := p.dbHandle.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := getAdminBucket(tx)
|
||||
bucket, err := getAdminsBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -368,7 +368,7 @@ func (p *BoltProvider) getAdmins(limit int, offset int, order string) ([]Admin,
|
|||
func (p *BoltProvider) dumpAdmins() ([]Admin, error) {
|
||||
admins := make([]Admin, 0, 30)
|
||||
err := p.dbHandle.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := getAdminBucket(tx)
|
||||
bucket, err := getAdminsBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -399,7 +399,7 @@ func (p *BoltProvider) userExists(username string) (User, error) {
|
|||
if u == nil {
|
||||
return &RecordNotFoundError{err: fmt.Sprintf("username %#v does not exist", username)}
|
||||
}
|
||||
folderBucket, err := getFolderBucket(tx)
|
||||
folderBucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -419,7 +419,7 @@ func (p *BoltProvider) addUser(user *User) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
folderBucket, err := getFolderBucket(tx)
|
||||
folderBucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -459,7 +459,7 @@ func (p *BoltProvider) updateUser(user *User) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
folderBucket, err := getFolderBucket(tx)
|
||||
folderBucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -504,7 +504,7 @@ func (p *BoltProvider) deleteUser(user *User) error {
|
|||
return err
|
||||
}
|
||||
if len(user.VirtualFolders) > 0 {
|
||||
folderBucket, err := getFolderBucket(tx)
|
||||
folderBucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -530,7 +530,7 @@ func (p *BoltProvider) dumpUsers() ([]User, error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
folderBucket, err := getFolderBucket(tx)
|
||||
folderBucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -562,7 +562,7 @@ func (p *BoltProvider) getUsers(limit int, offset int, order string) ([]User, er
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
folderBucket, err := getFolderBucket(tx)
|
||||
folderBucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -607,7 +607,7 @@ func (p *BoltProvider) getUsers(limit int, offset int, order string) ([]User, er
|
|||
func (p *BoltProvider) dumpFolders() ([]vfs.BaseVirtualFolder, error) {
|
||||
folders := make([]vfs.BaseVirtualFolder, 0, 50)
|
||||
err := p.dbHandle.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := getFolderBucket(tx)
|
||||
bucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -632,7 +632,7 @@ func (p *BoltProvider) getFolders(limit, offset int, order string) ([]vfs.BaseVi
|
|||
return folders, err
|
||||
}
|
||||
err = p.dbHandle.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := getFolderBucket(tx)
|
||||
bucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -681,7 +681,7 @@ func (p *BoltProvider) getFolders(limit, offset int, order string) ([]vfs.BaseVi
|
|||
func (p *BoltProvider) getFolderByName(name string) (vfs.BaseVirtualFolder, error) {
|
||||
var folder vfs.BaseVirtualFolder
|
||||
err := p.dbHandle.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := getFolderBucket(tx)
|
||||
bucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -697,7 +697,7 @@ func (p *BoltProvider) addFolder(folder *vfs.BaseVirtualFolder) error {
|
|||
return err
|
||||
}
|
||||
return p.dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getFolderBucket(tx)
|
||||
bucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -715,7 +715,7 @@ func (p *BoltProvider) updateFolder(folder *vfs.BaseVirtualFolder) error {
|
|||
return err
|
||||
}
|
||||
return p.dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getFolderBucket(tx)
|
||||
bucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -745,7 +745,7 @@ func (p *BoltProvider) updateFolder(folder *vfs.BaseVirtualFolder) error {
|
|||
|
||||
func (p *BoltProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
|
||||
return p.dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getFolderBucket(tx)
|
||||
bucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -795,7 +795,7 @@ func (p *BoltProvider) deleteFolder(folder *vfs.BaseVirtualFolder) error {
|
|||
|
||||
func (p *BoltProvider) updateFolderQuota(name string, filesAdd int, sizeAdd int64, reset bool) error {
|
||||
return p.dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getFolderBucket(tx)
|
||||
bucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -860,6 +860,8 @@ func (p *BoltProvider) migrateDatabase() error {
|
|||
providerLog(logger.LevelError, "%v", err)
|
||||
logger.ErrorToConsole("%v", err)
|
||||
return err
|
||||
case version == 6:
|
||||
return updateBoltDatabaseFrom6To10(p.dbHandle)
|
||||
default:
|
||||
if version > boltDatabaseVersion {
|
||||
providerLog(logger.LevelWarn, "database version %v is newer than the supported one: %v", version,
|
||||
|
@ -883,6 +885,9 @@ func (p *BoltProvider) revertDatabase(targetVersion int) error {
|
|||
if dbVersion.Version == targetVersion {
|
||||
return errors.New("current version match target version, nothing to do")
|
||||
}
|
||||
if dbVersion.Version == 10 {
|
||||
return downgradeBoltDatabaseFrom10To6(p.dbHandle)
|
||||
}
|
||||
return errors.New("the current version cannot be reverted")
|
||||
}
|
||||
|
||||
|
@ -991,7 +996,7 @@ func removeUserFromFolderMapping(folder *vfs.VirtualFolder, user *User, bucket *
|
|||
return err
|
||||
}
|
||||
|
||||
func getAdminBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
||||
func getAdminsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
||||
var err error
|
||||
|
||||
bucket := tx.Bucket(adminsBucket)
|
||||
|
@ -1010,7 +1015,7 @@ func getUsersBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
|||
return bucket, err
|
||||
}
|
||||
|
||||
func getFolderBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
||||
func getFoldersBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
||||
var err error
|
||||
bucket := tx.Bucket(foldersBucket)
|
||||
if bucket == nil {
|
||||
|
@ -1038,7 +1043,7 @@ func getBoltDatabaseVersion(dbHandle *bolt.DB) (schemaVersion, error) {
|
|||
return dbVersion, err
|
||||
}
|
||||
|
||||
/*func updateBoltDatabaseVersion(dbHandle *bolt.DB, version int) error {
|
||||
func updateBoltDatabaseVersion(dbHandle *bolt.DB, version int) error {
|
||||
err := dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket(dbVersionBucket)
|
||||
if bucket == nil {
|
||||
|
@ -1054,4 +1059,317 @@ func getBoltDatabaseVersion(dbHandle *bolt.DB) (schemaVersion, error) {
|
|||
return bucket.Put(dbVersionKey, buf)
|
||||
})
|
||||
return err
|
||||
}*/
|
||||
}
|
||||
|
||||
func updateBoltDatabaseFrom6To10(dbHandle *bolt.DB) error {
|
||||
logger.InfoToConsole("updating database version: 6 -> 10")
|
||||
providerLog(logger.LevelInfo, "updating database version: 6 -> 10")
|
||||
|
||||
if err := boltUpdateV7Folders(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := boltUpdateV7Users(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return updateBoltDatabaseVersion(dbHandle, 10)
|
||||
}
|
||||
|
||||
func downgradeBoltDatabaseFrom10To6(dbHandle *bolt.DB) error {
|
||||
logger.InfoToConsole("downgrading database version: 10 -> 6")
|
||||
providerLog(logger.LevelInfo, "downgrading database version: 10 -> 6")
|
||||
|
||||
if err := boltDowngradeV7Folders(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := boltDowngradeV7Users(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return updateBoltDatabaseVersion(dbHandle, 6)
|
||||
}
|
||||
|
||||
func boltUpdateV7Folders(dbHandle *bolt.DB) error {
|
||||
var folders []map[string]interface{}
|
||||
err := dbHandle.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var folderMap map[string]interface{}
|
||||
err = json.Unmarshal(v, &folderMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fsBytes, err := json.Marshal(folderMap["filesystem"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var compatFsConfig compatFilesystemV9
|
||||
err = json.Unmarshal(fsBytes, &compatFsConfig)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("failed to unmarshal v9 fsconfig for folder %#v, is it already migrated?", folderMap["name"])
|
||||
continue
|
||||
}
|
||||
if compatFsConfig.AzBlobConfig.SASURL != "" {
|
||||
folder := vfs.BaseVirtualFolder{
|
||||
Name: folderMap["name"].(string),
|
||||
}
|
||||
fsConfig, err := convertFsConfigFromV9(compatFsConfig, folder.GetEncrytionAdditionalData())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
folderMap["filesystem"] = fsConfig
|
||||
folders = append(folders, folderMap)
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, folder := range folders {
|
||||
buf, err := json.Marshal(folder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bucket.Put([]byte(folder["name"].(string)), buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func boltUpdateV7Users(dbHandle *bolt.DB) error {
|
||||
var users []map[string]interface{}
|
||||
err := dbHandle.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := getUsersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var userMap map[string]interface{}
|
||||
err = json.Unmarshal(v, &userMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fsBytes, err := json.Marshal(userMap["filesystem"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
foldersBytes, err := json.Marshal(userMap["virtual_folders"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var compatFsConfig compatFilesystemV9
|
||||
err = json.Unmarshal(fsBytes, &compatFsConfig)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("failed to unmarshal v9 fsconfig for user %#v, is it already migrated?", userMap["name"])
|
||||
continue
|
||||
}
|
||||
var compatFolders []compatFolderV9
|
||||
err = json.Unmarshal(foldersBytes, &compatFolders)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("failed to unmarshal v9 folders for user %#v, is it already migrated?", userMap["name"])
|
||||
continue
|
||||
}
|
||||
toConvert := false
|
||||
for idx := range compatFolders {
|
||||
f := &compatFolders[idx]
|
||||
if f.FsConfig.AzBlobConfig.SASURL != "" {
|
||||
f.FsConfig.AzBlobConfig = compatAzBlobFsConfigV9{}
|
||||
toConvert = true
|
||||
}
|
||||
}
|
||||
if compatFsConfig.AzBlobConfig.SASURL != "" {
|
||||
user := User{
|
||||
Username: userMap["username"].(string),
|
||||
}
|
||||
fsConfig, err := convertFsConfigFromV9(compatFsConfig, user.GetEncrytionAdditionalData())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userMap["filesystem"] = fsConfig
|
||||
toConvert = true
|
||||
}
|
||||
if toConvert {
|
||||
userMap["virtual_folders"] = compatFolders
|
||||
users = append(users, userMap)
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getUsersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, user := range users {
|
||||
buf, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bucket.Put([]byte(user["username"].(string)), buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:dupl
|
||||
func boltDowngradeV7Folders(dbHandle *bolt.DB) error {
|
||||
var folders []map[string]interface{}
|
||||
err := dbHandle.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var folderMap map[string]interface{}
|
||||
err = json.Unmarshal(v, &folderMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fsBytes, err := json.Marshal(folderMap["filesystem"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var fsConfig vfs.Filesystem
|
||||
err = json.Unmarshal(fsBytes, &fsConfig)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("failed to unmarshal v10 fsconfig for folder %#v, is it already migrated?", folderMap["name"])
|
||||
continue
|
||||
}
|
||||
if fsConfig.AzBlobConfig.SASURL != nil && !fsConfig.AzBlobConfig.SASURL.IsEmpty() {
|
||||
fsV9, err := convertFsConfigToV9(fsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
folderMap["filesystem"] = fsV9
|
||||
folders = append(folders, folderMap)
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getFoldersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, folder := range folders {
|
||||
buf, err := json.Marshal(folder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bucket.Put([]byte(folder["name"].(string)), buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:dupl,gocyclo
|
||||
func boltDowngradeV7Users(dbHandle *bolt.DB) error {
|
||||
var users []map[string]interface{}
|
||||
err := dbHandle.View(func(tx *bolt.Tx) error {
|
||||
bucket, err := getUsersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cursor := bucket.Cursor()
|
||||
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
|
||||
var userMap map[string]interface{}
|
||||
err = json.Unmarshal(v, &userMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fsBytes, err := json.Marshal(userMap["filesystem"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
foldersBytes, err := json.Marshal(userMap["virtual_folders"])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var fsConfig vfs.Filesystem
|
||||
err = json.Unmarshal(fsBytes, &fsConfig)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("failed to unmarshal v10 fsconfig for user %#v, is it already migrated?", userMap["username"])
|
||||
continue
|
||||
}
|
||||
var folders []vfs.VirtualFolder
|
||||
err = json.Unmarshal(foldersBytes, &folders)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("failed to unmarshal v9 folders for user %#v, is it already migrated?", userMap["name"])
|
||||
continue
|
||||
}
|
||||
toConvert := false
|
||||
for idx := range folders {
|
||||
f := &folders[idx]
|
||||
f.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{}
|
||||
toConvert = true
|
||||
}
|
||||
if fsConfig.AzBlobConfig.SASURL != nil && !fsConfig.AzBlobConfig.SASURL.IsEmpty() {
|
||||
fsV9, err := convertFsConfigToV9(fsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userMap["filesystem"] = fsV9
|
||||
toConvert = true
|
||||
}
|
||||
if toConvert {
|
||||
userMap["virtual_folders"] = folders
|
||||
users = append(users, userMap)
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbHandle.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := getUsersBucket(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, user := range users {
|
||||
buf, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bucket.Put([]byte(user["username"].(string)), buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
118
dataprovider/compat.go
Normal file
118
dataprovider/compat.go
Normal file
|
@ -0,0 +1,118 @@
|
|||
package dataprovider
|
||||
|
||||
import (
|
||||
"github.com/drakkan/sftpgo/kms"
|
||||
"github.com/drakkan/sftpgo/vfs"
|
||||
)
|
||||
|
||||
type compatAzBlobFsConfigV9 struct {
|
||||
Container string `json:"container,omitempty"`
|
||||
AccountName string `json:"account_name,omitempty"`
|
||||
AccountKey *kms.Secret `json:"account_key,omitempty"`
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
SASURL string `json:"sas_url,omitempty"`
|
||||
KeyPrefix string `json:"key_prefix,omitempty"`
|
||||
UploadPartSize int64 `json:"upload_part_size,omitempty"`
|
||||
UploadConcurrency int `json:"upload_concurrency,omitempty"`
|
||||
UseEmulator bool `json:"use_emulator,omitempty"`
|
||||
AccessTier string `json:"access_tier,omitempty"`
|
||||
}
|
||||
|
||||
type compatFilesystemV9 struct {
|
||||
Provider vfs.FilesystemProvider `json:"provider"`
|
||||
S3Config vfs.S3FsConfig `json:"s3config,omitempty"`
|
||||
GCSConfig vfs.GCSFsConfig `json:"gcsconfig,omitempty"`
|
||||
AzBlobConfig compatAzBlobFsConfigV9 `json:"azblobconfig,omitempty"`
|
||||
CryptConfig vfs.CryptFsConfig `json:"cryptconfig,omitempty"`
|
||||
SFTPConfig vfs.SFTPFsConfig `json:"sftpconfig,omitempty"`
|
||||
}
|
||||
|
||||
type compatBaseFolderV9 struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MappedPath string `json:"mapped_path,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
UsedQuotaSize int64 `json:"used_quota_size"`
|
||||
UsedQuotaFiles int `json:"used_quota_files"`
|
||||
LastQuotaUpdate int64 `json:"last_quota_update"`
|
||||
Users []string `json:"users,omitempty"`
|
||||
FsConfig compatFilesystemV9 `json:"filesystem"`
|
||||
}
|
||||
|
||||
type compatFolderV9 struct {
|
||||
compatBaseFolderV9
|
||||
VirtualPath string `json:"virtual_path"`
|
||||
QuotaSize int64 `json:"quota_size"`
|
||||
QuotaFiles int `json:"quota_files"`
|
||||
}
|
||||
|
||||
type compatUserV9 struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
FsConfig compatFilesystemV9 `json:"filesystem"`
|
||||
}
|
||||
|
||||
func convertFsConfigFromV9(compatFs compatFilesystemV9, aead string) (vfs.Filesystem, error) {
|
||||
fsConfig := vfs.Filesystem{
|
||||
Provider: compatFs.Provider,
|
||||
S3Config: compatFs.S3Config,
|
||||
GCSConfig: compatFs.GCSConfig,
|
||||
CryptConfig: compatFs.CryptConfig,
|
||||
SFTPConfig: compatFs.SFTPConfig,
|
||||
}
|
||||
azSASURL := kms.NewEmptySecret()
|
||||
if compatFs.Provider == vfs.AzureBlobFilesystemProvider && compatFs.AzBlobConfig.SASURL != "" {
|
||||
azSASURL = kms.NewPlainSecret(compatFs.AzBlobConfig.SASURL)
|
||||
}
|
||||
if compatFs.AzBlobConfig.AccountKey == nil {
|
||||
compatFs.AzBlobConfig.AccountKey = kms.NewEmptySecret()
|
||||
}
|
||||
fsConfig.AzBlobConfig = vfs.AzBlobFsConfig{
|
||||
Container: compatFs.AzBlobConfig.Container,
|
||||
AccountName: compatFs.AzBlobConfig.AccountName,
|
||||
AccountKey: compatFs.AzBlobConfig.AccountKey,
|
||||
Endpoint: compatFs.AzBlobConfig.Endpoint,
|
||||
SASURL: azSASURL,
|
||||
KeyPrefix: compatFs.AzBlobConfig.KeyPrefix,
|
||||
UploadPartSize: compatFs.AzBlobConfig.UploadPartSize,
|
||||
UploadConcurrency: compatFs.AzBlobConfig.UploadConcurrency,
|
||||
UseEmulator: compatFs.AzBlobConfig.UseEmulator,
|
||||
AccessTier: compatFs.AzBlobConfig.AccessTier,
|
||||
}
|
||||
err := fsConfig.AzBlobConfig.EncryptCredentials(aead)
|
||||
return fsConfig, err
|
||||
}
|
||||
|
||||
func convertFsConfigToV9(fs vfs.Filesystem) (compatFilesystemV9, error) {
|
||||
azSASURL := ""
|
||||
if fs.Provider == vfs.AzureBlobFilesystemProvider {
|
||||
if fs.AzBlobConfig.SASURL != nil && fs.AzBlobConfig.SASURL.IsEncrypted() {
|
||||
err := fs.AzBlobConfig.SASURL.Decrypt()
|
||||
if err != nil {
|
||||
return compatFilesystemV9{}, err
|
||||
}
|
||||
azSASURL = fs.AzBlobConfig.SASURL.GetPayload()
|
||||
}
|
||||
}
|
||||
azFsCompat := compatAzBlobFsConfigV9{
|
||||
Container: fs.AzBlobConfig.Container,
|
||||
AccountName: fs.AzBlobConfig.AccountName,
|
||||
AccountKey: fs.AzBlobConfig.AccountKey,
|
||||
Endpoint: fs.AzBlobConfig.Endpoint,
|
||||
SASURL: azSASURL,
|
||||
KeyPrefix: fs.AzBlobConfig.KeyPrefix,
|
||||
UploadPartSize: fs.AzBlobConfig.UploadPartSize,
|
||||
UploadConcurrency: fs.AzBlobConfig.UploadConcurrency,
|
||||
UseEmulator: fs.AzBlobConfig.UseEmulator,
|
||||
AccessTier: fs.AzBlobConfig.AccessTier,
|
||||
}
|
||||
fsV9 := compatFilesystemV9{
|
||||
Provider: fs.Provider,
|
||||
S3Config: fs.S3Config,
|
||||
GCSConfig: fs.GCSConfig,
|
||||
AzBlobConfig: azFsCompat,
|
||||
CryptConfig: fs.CryptConfig,
|
||||
SFTPConfig: fs.SFTPConfig,
|
||||
}
|
||||
return fsV9, nil
|
||||
}
|
|
@ -66,7 +66,7 @@ const (
|
|||
CockroachDataProviderName = "cockroachdb"
|
||||
// DumpVersion defines the version for the dump.
|
||||
// For restore/load we support the current version and the previous one
|
||||
DumpVersion = 7
|
||||
DumpVersion = 8
|
||||
|
||||
argonPwdPrefix = "$argon2id$"
|
||||
bcryptPwdPrefix = "$2a$"
|
||||
|
|
|
@ -250,6 +250,8 @@ func (p *MySQLProvider) migrateDatabase() error {
|
|||
return err
|
||||
case version == 8:
|
||||
return updateMySQLDatabaseFromV8(p.dbHandle)
|
||||
case version == 9:
|
||||
return updateMySQLDatabaseFromV9(p.dbHandle)
|
||||
default:
|
||||
if version > sqlDatabaseVersion {
|
||||
providerLog(logger.LevelWarn, "database version %v is newer than the supported one: %v", version,
|
||||
|
@ -274,19 +276,35 @@ func (p *MySQLProvider) revertDatabase(targetVersion int) error {
|
|||
switch dbVersion.Version {
|
||||
case 9:
|
||||
return downgradeMySQLDatabaseFromV9(p.dbHandle)
|
||||
case 10:
|
||||
return downgradeMySQLDatabaseFromV10(p.dbHandle)
|
||||
default:
|
||||
return fmt.Errorf("database version not handled: %v", dbVersion.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func updateMySQLDatabaseFromV8(dbHandle *sql.DB) error {
|
||||
return updateMySQLDatabaseFrom8To9(dbHandle)
|
||||
if err := updateMySQLDatabaseFrom8To9(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return updateMySQLDatabaseFromV9(dbHandle)
|
||||
}
|
||||
|
||||
func updateMySQLDatabaseFromV9(dbHandle *sql.DB) error {
|
||||
return updateMySQLDatabaseFrom9To10(dbHandle)
|
||||
}
|
||||
|
||||
func downgradeMySQLDatabaseFromV9(dbHandle *sql.DB) error {
|
||||
return downgradeMySQLDatabaseFrom9To8(dbHandle)
|
||||
}
|
||||
|
||||
func downgradeMySQLDatabaseFromV10(dbHandle *sql.DB) error {
|
||||
if err := downgradeMySQLDatabaseFrom10To9(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return downgradeMySQLDatabaseFromV9(dbHandle)
|
||||
}
|
||||
|
||||
func updateMySQLDatabaseFrom8To9(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 8 -> 9")
|
||||
providerLog(logger.LevelInfo, "updating database version: 8 -> 9")
|
||||
|
@ -304,3 +322,11 @@ func downgradeMySQLDatabaseFrom9To8(dbHandle *sql.DB) error {
|
|||
sql = strings.ReplaceAll(sql, "{{folders}}", sqlTableFolders)
|
||||
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, strings.Split(sql, ";"), 8)
|
||||
}
|
||||
|
||||
func updateMySQLDatabaseFrom9To10(dbHandle *sql.DB) error {
|
||||
return sqlCommonUpdateDatabaseFrom9To10(dbHandle)
|
||||
}
|
||||
|
||||
func downgradeMySQLDatabaseFrom10To9(dbHandle *sql.DB) error {
|
||||
return sqlCommonDowngradeDatabaseFrom10To9(dbHandle)
|
||||
}
|
||||
|
|
|
@ -263,6 +263,8 @@ func (p *PGSQLProvider) migrateDatabase() error {
|
|||
return err
|
||||
case version == 8:
|
||||
return updatePGSQLDatabaseFromV8(p.dbHandle)
|
||||
case version == 9:
|
||||
return updatePGSQLDatabaseFromV9(p.dbHandle)
|
||||
default:
|
||||
if version > sqlDatabaseVersion {
|
||||
providerLog(logger.LevelWarn, "database version %v is newer than the supported one: %v", version,
|
||||
|
@ -287,19 +289,35 @@ func (p *PGSQLProvider) revertDatabase(targetVersion int) error {
|
|||
switch dbVersion.Version {
|
||||
case 9:
|
||||
return downgradePGSQLDatabaseFromV9(p.dbHandle)
|
||||
case 10:
|
||||
return downgradePGSQLDatabaseFromV10(p.dbHandle)
|
||||
default:
|
||||
return fmt.Errorf("database version not handled: %v", dbVersion.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func updatePGSQLDatabaseFromV8(dbHandle *sql.DB) error {
|
||||
return updatePGSQLDatabaseFrom8To9(dbHandle)
|
||||
if err := updatePGSQLDatabaseFrom8To9(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return updatePGSQLDatabaseFromV9(dbHandle)
|
||||
}
|
||||
|
||||
func updatePGSQLDatabaseFromV9(dbHandle *sql.DB) error {
|
||||
return updatePGSQLDatabaseFrom9To10(dbHandle)
|
||||
}
|
||||
|
||||
func downgradePGSQLDatabaseFromV9(dbHandle *sql.DB) error {
|
||||
return downgradePGSQLDatabaseFrom9To8(dbHandle)
|
||||
}
|
||||
|
||||
func downgradePGSQLDatabaseFromV10(dbHandle *sql.DB) error {
|
||||
if err := downgradePGSQLDatabaseFrom10To9(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return downgradePGSQLDatabaseFromV9(dbHandle)
|
||||
}
|
||||
|
||||
func updatePGSQLDatabaseFrom8To9(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 8 -> 9")
|
||||
providerLog(logger.LevelInfo, "updating database version: 8 -> 9")
|
||||
|
@ -317,3 +335,11 @@ func downgradePGSQLDatabaseFrom9To8(dbHandle *sql.DB) error {
|
|||
sql = strings.ReplaceAll(sql, "{{folders}}", sqlTableFolders)
|
||||
return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 8)
|
||||
}
|
||||
|
||||
func updatePGSQLDatabaseFrom9To10(dbHandle *sql.DB) error {
|
||||
return sqlCommonUpdateDatabaseFrom9To10(dbHandle)
|
||||
}
|
||||
|
||||
func downgradePGSQLDatabaseFrom10To9(dbHandle *sql.DB) error {
|
||||
return sqlCommonDowngradeDatabaseFrom10To9(dbHandle)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
sqlDatabaseVersion = 9
|
||||
sqlDatabaseVersion = 10
|
||||
defaultSQLQueryTimeout = 10 * time.Second
|
||||
longSQLQueryTimeout = 60 * time.Second
|
||||
)
|
||||
|
@ -1096,3 +1096,313 @@ func sqlCommonExecuteTx(ctx context.Context, dbHandle *sql.DB, txFn func(*sql.Tx
|
|||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func sqlCommonUpdateDatabaseFrom9To10(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 9 -> 10")
|
||||
providerLog(logger.LevelInfo, "updating database version: 9 -> 10")
|
||||
|
||||
if err := sqlCommonUpdateV10Folders(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sqlCommonUpdateV10Users(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
return sqlCommonUpdateDatabaseVersion(ctx, dbHandle, 10)
|
||||
}
|
||||
|
||||
func sqlCommonDowngradeDatabaseFrom10To9(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("downgrading database version: 10 -> 9")
|
||||
providerLog(logger.LevelInfo, "downgrading database version: 10 -> 9")
|
||||
|
||||
if err := sqlCommonDowngradeV10Folders(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sqlCommonDowngradeV10Users(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultSQLQueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
return sqlCommonUpdateDatabaseVersion(ctx, dbHandle, 9)
|
||||
}
|
||||
|
||||
//nolint:dupl
|
||||
func sqlCommonDowngradeV10Folders(dbHandle *sql.DB) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), longSQLQueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
q := getCompatFolderV10FsConfigQuery()
|
||||
stmt, err := dbHandle.PrepareContext(ctx, q)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err)
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
rows, err := stmt.QueryContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var folders []compatBaseFolderV9
|
||||
for rows.Next() {
|
||||
var folder compatBaseFolderV9
|
||||
var fsConfigString sql.NullString
|
||||
err = rows.Scan(&folder.ID, &folder.Name, &fsConfigString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fsConfigString.Valid {
|
||||
var fsConfig vfs.Filesystem
|
||||
err = json.Unmarshal([]byte(fsConfigString.String), &fsConfig)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("failed to unmarshal v10 fsconfig for folder %#v, is it already migrated?", folder.Name)
|
||||
continue
|
||||
}
|
||||
if fsConfig.AzBlobConfig.SASURL != nil && !fsConfig.AzBlobConfig.SASURL.IsEmpty() {
|
||||
fsV9, err := convertFsConfigToV9(fsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
folder.FsConfig = fsV9
|
||||
folders = append(folders, folder)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
// update fsconfig for affected folders
|
||||
for _, folder := range folders {
|
||||
q := updateCompatFolderV10FsConfigQuery()
|
||||
stmt, err := dbHandle.PrepareContext(ctx, q)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err)
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
cfg, err := json.Marshal(folder.FsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.ExecContext(ctx, string(cfg), folder.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:dupl
|
||||
func sqlCommonDowngradeV10Users(dbHandle *sql.DB) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), longSQLQueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
q := getCompatUserV10FsConfigQuery()
|
||||
stmt, err := dbHandle.PrepareContext(ctx, q)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err)
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
rows, err := stmt.QueryContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []compatUserV9
|
||||
for rows.Next() {
|
||||
var user compatUserV9
|
||||
var fsConfigString sql.NullString
|
||||
err = rows.Scan(&user.ID, &user.Username, &fsConfigString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fsConfigString.Valid {
|
||||
var fsConfig vfs.Filesystem
|
||||
err = json.Unmarshal([]byte(fsConfigString.String), &fsConfig)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("failed to unmarshal v10 fsconfig for user %#v, is it already migrated?", user.Username)
|
||||
continue
|
||||
}
|
||||
if fsConfig.AzBlobConfig.SASURL != nil && !fsConfig.AzBlobConfig.SASURL.IsEmpty() {
|
||||
fsV9, err := convertFsConfigToV9(fsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.FsConfig = fsV9
|
||||
users = append(users, user)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
// update fsconfig for affected users
|
||||
for _, user := range users {
|
||||
q := updateCompatUserV10FsConfigQuery()
|
||||
stmt, err := dbHandle.PrepareContext(ctx, q)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err)
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
cfg, err := json.Marshal(user.FsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.ExecContext(ctx, string(cfg), user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sqlCommonUpdateV10Folders(dbHandle *sql.DB) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), longSQLQueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
q := getCompatFolderV10FsConfigQuery()
|
||||
stmt, err := dbHandle.PrepareContext(ctx, q)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err)
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
rows, err := stmt.QueryContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var folders []vfs.BaseVirtualFolder
|
||||
for rows.Next() {
|
||||
var folder vfs.BaseVirtualFolder
|
||||
var fsConfigString sql.NullString
|
||||
err = rows.Scan(&folder.ID, &folder.Name, &fsConfigString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fsConfigString.Valid {
|
||||
var compatFsConfig compatFilesystemV9
|
||||
err = json.Unmarshal([]byte(fsConfigString.String), &compatFsConfig)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("failed to unmarshal v9 fsconfig for folder %#v, is it already migrated?", folder.Name)
|
||||
continue
|
||||
}
|
||||
if compatFsConfig.AzBlobConfig.SASURL != "" {
|
||||
fsConfig, err := convertFsConfigFromV9(compatFsConfig, folder.GetEncrytionAdditionalData())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
folder.FsConfig = fsConfig
|
||||
folders = append(folders, folder)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
// update fsconfig for affected folders
|
||||
for _, folder := range folders {
|
||||
q := updateCompatFolderV10FsConfigQuery()
|
||||
stmt, err := dbHandle.PrepareContext(ctx, q)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err)
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
cfg, err := json.Marshal(folder.FsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.ExecContext(ctx, string(cfg), folder.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sqlCommonUpdateV10Users(dbHandle *sql.DB) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), longSQLQueryTimeout)
|
||||
defer cancel()
|
||||
|
||||
q := getCompatUserV10FsConfigQuery()
|
||||
stmt, err := dbHandle.PrepareContext(ctx, q)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err)
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
rows, err := stmt.QueryContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users []User
|
||||
for rows.Next() {
|
||||
var user User
|
||||
var fsConfigString sql.NullString
|
||||
err = rows.Scan(&user.ID, &user.Username, &fsConfigString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fsConfigString.Valid {
|
||||
var compatFsConfig compatFilesystemV9
|
||||
err = json.Unmarshal([]byte(fsConfigString.String), &compatFsConfig)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("failed to unmarshal v9 fsconfig for user %#v, is it already migrated?", user.Username)
|
||||
continue
|
||||
}
|
||||
if compatFsConfig.AzBlobConfig.SASURL != "" {
|
||||
fsConfig, err := convertFsConfigFromV9(compatFsConfig, user.GetEncrytionAdditionalData())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.FsConfig = fsConfig
|
||||
users = append(users, user)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
// update fsconfig for affected users
|
||||
for _, user := range users {
|
||||
q := updateCompatUserV10FsConfigQuery()
|
||||
stmt, err := dbHandle.PrepareContext(ctx, q)
|
||||
if err != nil {
|
||||
providerLog(logger.LevelWarn, "error preparing database query %#v: %v", q, err)
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
cfg, err := json.Marshal(user.FsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = stmt.ExecContext(ctx, string(cfg), user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -271,6 +271,8 @@ func (p *SQLiteProvider) migrateDatabase() error {
|
|||
return err
|
||||
case version == 8:
|
||||
return updateSQLiteDatabaseFromV8(p.dbHandle)
|
||||
case version == 9:
|
||||
return updateSQLiteDatabaseFromV9(p.dbHandle)
|
||||
default:
|
||||
if version > sqlDatabaseVersion {
|
||||
providerLog(logger.LevelWarn, "database version %v is newer than the supported one: %v", version,
|
||||
|
@ -295,19 +297,35 @@ func (p *SQLiteProvider) revertDatabase(targetVersion int) error {
|
|||
switch dbVersion.Version {
|
||||
case 9:
|
||||
return downgradeSQLiteDatabaseFromV9(p.dbHandle)
|
||||
case 10:
|
||||
return downgradeSQLiteDatabaseFromV10(p.dbHandle)
|
||||
default:
|
||||
return fmt.Errorf("database version not handled: %v", dbVersion.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSQLiteDatabaseFromV8(dbHandle *sql.DB) error {
|
||||
return updateSQLiteDatabaseFrom8To9(dbHandle)
|
||||
if err := updateSQLiteDatabaseFrom8To9(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return updateSQLiteDatabaseFromV9(dbHandle)
|
||||
}
|
||||
|
||||
func updateSQLiteDatabaseFromV9(dbHandle *sql.DB) error {
|
||||
return updateSQLiteDatabaseFrom9To10(dbHandle)
|
||||
}
|
||||
|
||||
func downgradeSQLiteDatabaseFromV9(dbHandle *sql.DB) error {
|
||||
return downgradeSQLiteDatabaseFrom9To8(dbHandle)
|
||||
}
|
||||
|
||||
func downgradeSQLiteDatabaseFromV10(dbHandle *sql.DB) error {
|
||||
if err := downgradeSQLiteDatabaseFrom10To9(dbHandle); err != nil {
|
||||
return err
|
||||
}
|
||||
return downgradeSQLiteDatabaseFromV9(dbHandle)
|
||||
}
|
||||
|
||||
func updateSQLiteDatabaseFrom8To9(dbHandle *sql.DB) error {
|
||||
logger.InfoToConsole("updating database version: 8 -> 9")
|
||||
providerLog(logger.LevelInfo, "updating database version: 8 -> 9")
|
||||
|
@ -332,6 +350,14 @@ func downgradeSQLiteDatabaseFrom9To8(dbHandle *sql.DB) error {
|
|||
return setPragmaFK(dbHandle, "ON")
|
||||
}
|
||||
|
||||
func updateSQLiteDatabaseFrom9To10(dbHandle *sql.DB) error {
|
||||
return sqlCommonUpdateDatabaseFrom9To10(dbHandle)
|
||||
}
|
||||
|
||||
func downgradeSQLiteDatabaseFrom10To9(dbHandle *sql.DB) error {
|
||||
return sqlCommonDowngradeDatabaseFrom10To9(dbHandle)
|
||||
}
|
||||
|
||||
func setPragmaFK(dbHandle *sql.DB, value string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), longSQLQueryTimeout)
|
||||
defer cancel()
|
||||
|
|
|
@ -210,3 +210,19 @@ func getDatabaseVersionQuery() string {
|
|||
func getUpdateDBVersionQuery() string {
|
||||
return fmt.Sprintf(`UPDATE %v SET version=%v`, sqlTableSchemaVersion, sqlPlaceholders[0])
|
||||
}
|
||||
|
||||
func getCompatUserV10FsConfigQuery() string {
|
||||
return fmt.Sprintf(`SELECT id,username,filesystem FROM %v`, sqlTableUsers)
|
||||
}
|
||||
|
||||
func updateCompatUserV10FsConfigQuery() string {
|
||||
return fmt.Sprintf(`UPDATE %v SET filesystem=%v WHERE id=%v`, sqlTableUsers, sqlPlaceholders[0], sqlPlaceholders[1])
|
||||
}
|
||||
|
||||
func getCompatFolderV10FsConfigQuery() string {
|
||||
return fmt.Sprintf(`SELECT id,name,filesystem FROM %v`, sqlTableFolders)
|
||||
}
|
||||
|
||||
func updateCompatFolderV10FsConfigQuery() string {
|
||||
return fmt.Sprintf(`UPDATE %v SET filesystem=%v WHERE id=%v`, sqlTableFolders, sqlPlaceholders[0], sqlPlaceholders[1])
|
||||
}
|
||||
|
|
|
@ -349,6 +349,7 @@ func (u *User) hideConfidentialData() {
|
|||
u.FsConfig.GCSConfig.Credentials.Hide()
|
||||
case vfs.AzureBlobFilesystemProvider:
|
||||
u.FsConfig.AzBlobConfig.AccountKey.Hide()
|
||||
u.FsConfig.AzBlobConfig.SASURL.Hide()
|
||||
case vfs.CryptedFilesystemProvider:
|
||||
u.FsConfig.CryptConfig.Passphrase.Hide()
|
||||
case vfs.SFTPFilesystemProvider:
|
||||
|
@ -399,6 +400,9 @@ func (u *User) hasRedactedSecret() bool {
|
|||
if u.FsConfig.AzBlobConfig.AccountKey.IsRedacted() {
|
||||
return true
|
||||
}
|
||||
if u.FsConfig.AzBlobConfig.SASURL.IsRedacted() {
|
||||
return true
|
||||
}
|
||||
case vfs.CryptedFilesystemProvider:
|
||||
if u.FsConfig.CryptConfig.Passphrase.IsRedacted() {
|
||||
return true
|
||||
|
@ -457,6 +461,7 @@ func (u *User) SetEmptySecrets() {
|
|||
u.FsConfig.S3Config.AccessSecret = kms.NewEmptySecret()
|
||||
u.FsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
|
||||
u.FsConfig.AzBlobConfig.AccountKey = kms.NewEmptySecret()
|
||||
u.FsConfig.AzBlobConfig.SASURL = kms.NewEmptySecret()
|
||||
u.FsConfig.CryptConfig.Passphrase = kms.NewEmptySecret()
|
||||
u.FsConfig.SFTPConfig.Password = kms.NewEmptySecret()
|
||||
u.FsConfig.SFTPConfig.PrivateKey = kms.NewEmptySecret()
|
||||
|
|
|
@ -40,7 +40,7 @@ The external program can also read the following environment variables:
|
|||
- `SFTPGO_ACTION_FILE_SIZE`, non-zero for `pre-upload`,`upload`, `download` and `delete` actions if the file size is greater than `0`
|
||||
- `SFTPGO_ACTION_FS_PROVIDER`, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend
|
||||
- `SFTPGO_ACTION_BUCKET`, non-empty for S3, GCS and Azure backends
|
||||
- `SFTPGO_ACTION_ENDPOINT`, non-empty for S3, SFTP and Azure backend if configured. For Azure this is the SAS URL, if configured otherwise the endpoint
|
||||
- `SFTPGO_ACTION_ENDPOINT`, non-empty for S3, SFTP and Azure backend if configured. For Azure this is the endpoint, if configured
|
||||
- `SFTPGO_ACTION_STATUS`, integer. Status for `upload`, `download` and `ssh_cmd` actions. 0 means a generic error occurred. 1 means no error, 2 means quota exceeded error
|
||||
- `SFTPGO_ACTION_PROTOCOL`, string. Possible values are `SSH`, `SFTP`, `SCP`, `FTP`, `DAV`, `HTTP`
|
||||
- `SFTPGO_ACTION_OPEN_FLAGS`, integer. File open flags, can be non-zero for `pre-upload` action. If `SFTPGO_ACTION_FILE_SIZE` is greater than zero and `SFTPGO_ACTION_OPEN_FLAGS&512 == 0` the target file will not be truncated
|
||||
|
@ -58,7 +58,7 @@ If the `hook` defines an HTTP URL then this URL will be invoked as HTTP POST. Th
|
|||
- `file_size`, included for `pre-upload`, `upload`, `download`, `delete` actions if the file size is greater than `0`
|
||||
- `fs_provider`, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend
|
||||
- `bucket`, inlcuded for S3, GCS and Azure backends
|
||||
- `endpoint`, included for S3, SFTP and Azure backend if configured. For Azure this is the SAS URL, if configured, otherwise the endpoint
|
||||
- `endpoint`, included for S3, SFTP and Azure backend if configured. For Azure this is the endpoint, if configured
|
||||
- `status`, integer. Status for `upload`, `download` and `ssh_cmd` actions. 0 means a generic error occurred. 1 means no error, 2 means quota exceeded error
|
||||
- `protocol`, string. Possible values are `SSH`, `SFTP`, `SCP`, `FTP`, `DAV`, `HTTP`
|
||||
- `open_flags`, integer. File open flags, can be non-zero for `pre-upload` action. If `file_size` is greater than zero and `file_size&512 == 0` the target file will not be truncated
|
||||
|
|
|
@ -54,6 +54,7 @@ func updateFolder(w http.ResponseWriter, r *http.Request) {
|
|||
folderID := folder.ID
|
||||
currentS3AccessSecret := folder.FsConfig.S3Config.AccessSecret
|
||||
currentAzAccountKey := folder.FsConfig.AzBlobConfig.AccountKey
|
||||
currentAzSASUrl := folder.FsConfig.AzBlobConfig.SASURL
|
||||
currentGCSCredentials := folder.FsConfig.GCSConfig.Credentials
|
||||
currentCryptoPassphrase := folder.FsConfig.CryptConfig.Passphrase
|
||||
currentSFTPPassword := folder.FsConfig.SFTPConfig.Password
|
||||
|
@ -72,7 +73,7 @@ func updateFolder(w http.ResponseWriter, r *http.Request) {
|
|||
folder.ID = folderID
|
||||
folder.Name = name
|
||||
folder.FsConfig.SetEmptySecretsIfNil()
|
||||
updateEncryptedSecrets(&folder.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentGCSCredentials,
|
||||
updateEncryptedSecrets(&folder.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl, currentGCSCredentials,
|
||||
currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey)
|
||||
err = dataprovider.UpdateFolder(&folder, users)
|
||||
if err != nil {
|
||||
|
|
|
@ -74,6 +74,10 @@ func addUser(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, errors.New("invalid account_key"), "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if user.FsConfig.AzBlobConfig.SASURL.IsRedacted() {
|
||||
sendAPIResponse(w, r, errors.New("invalid sas_url"), "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
case vfs.CryptedFilesystemProvider:
|
||||
if user.FsConfig.CryptConfig.Passphrase.IsRedacted() {
|
||||
sendAPIResponse(w, r, errors.New("invalid passphrase"), "", http.StatusBadRequest)
|
||||
|
@ -120,6 +124,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
|
|||
currentPermissions := user.Permissions
|
||||
currentS3AccessSecret := user.FsConfig.S3Config.AccessSecret
|
||||
currentAzAccountKey := user.FsConfig.AzBlobConfig.AccountKey
|
||||
currentAzSASUrl := user.FsConfig.AzBlobConfig.SASURL
|
||||
currentGCSCredentials := user.FsConfig.GCSConfig.Credentials
|
||||
currentCryptoPassphrase := user.FsConfig.CryptConfig.Passphrase
|
||||
currentSFTPPassword := user.FsConfig.SFTPConfig.Password
|
||||
|
@ -144,8 +149,8 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
|
|||
if len(user.Permissions) == 0 {
|
||||
user.Permissions = currentPermissions
|
||||
}
|
||||
updateEncryptedSecrets(&user.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentGCSCredentials, currentCryptoPassphrase,
|
||||
currentSFTPPassword, currentSFTPKey)
|
||||
updateEncryptedSecrets(&user.FsConfig, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl,
|
||||
currentGCSCredentials, currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey)
|
||||
err = dataprovider.UpdateUser(&user)
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
|
@ -176,7 +181,7 @@ func disconnectUser(username string) {
|
|||
}
|
||||
}
|
||||
|
||||
func updateEncryptedSecrets(fsConfig *vfs.Filesystem, currentS3AccessSecret, currentAzAccountKey,
|
||||
func updateEncryptedSecrets(fsConfig *vfs.Filesystem, currentS3AccessSecret, currentAzAccountKey, currentAzSASUrl,
|
||||
currentGCSCredentials, currentCryptoPassphrase, currentSFTPPassword, currentSFTPKey *kms.Secret) {
|
||||
// we use the new access secret if plain or empty, otherwise the old value
|
||||
switch fsConfig.Provider {
|
||||
|
@ -188,6 +193,9 @@ func updateEncryptedSecrets(fsConfig *vfs.Filesystem, currentS3AccessSecret, cur
|
|||
if fsConfig.AzBlobConfig.AccountKey.IsNotPlainAndNotEmpty() {
|
||||
fsConfig.AzBlobConfig.AccountKey = currentAzAccountKey
|
||||
}
|
||||
if fsConfig.AzBlobConfig.SASURL.IsNotPlainAndNotEmpty() {
|
||||
fsConfig.AzBlobConfig.SASURL = currentAzSASUrl
|
||||
}
|
||||
case vfs.GCSFilesystemProvider:
|
||||
if fsConfig.GCSConfig.Credentials.IsNotPlainAndNotEmpty() {
|
||||
fsConfig.GCSConfig.Credentials = currentGCSCredentials
|
||||
|
|
|
@ -984,10 +984,13 @@ func TestAddUserInvalidFsConfig(t *testing.T) {
|
|||
|
||||
u = getTestUser()
|
||||
u.FsConfig.Provider = vfs.AzureBlobFilesystemProvider
|
||||
u.FsConfig.AzBlobConfig.SASURL = "http://foo\x7f.com/"
|
||||
u.FsConfig.AzBlobConfig.SASURL = kms.NewPlainSecret("http://foo\x7f.com/")
|
||||
_, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
u.FsConfig.AzBlobConfig.SASURL = ""
|
||||
u.FsConfig.AzBlobConfig.SASURL = kms.NewSecret(kms.SecretStatusRedacted, "key", "", "")
|
||||
_, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
u.FsConfig.AzBlobConfig.SASURL = kms.NewEmptySecret()
|
||||
u.FsConfig.AzBlobConfig.AccountName = "name"
|
||||
_, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1802,9 +1805,9 @@ func TestUserAzureBlobConfig(t *testing.T) {
|
|||
assert.Equal(t, initialPayload, user.FsConfig.AzBlobConfig.AccountKey.GetPayload())
|
||||
assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
|
||||
assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetKey())
|
||||
// test user without access key and access secret (sas)
|
||||
// test user without access key and access secret (SAS)
|
||||
user.FsConfig.Provider = vfs.AzureBlobFilesystemProvider
|
||||
user.FsConfig.AzBlobConfig.SASURL = "https://myaccount.blob.core.windows.net/pictures/profile.jpg?sv=2012-02-12&st=2009-02-09&se=2009-02-10&sr=c&sp=r&si=YWJjZGVmZw%3d%3d&sig=dD80ihBh5jfNpymO5Hg1IdiJIEvHcJpCMiCMnN%2fRnbI%3d"
|
||||
user.FsConfig.AzBlobConfig.SASURL = kms.NewPlainSecret("https://myaccount.blob.core.windows.net/pictures/profile.jpg?sv=2012-02-12&st=2009-02-09&se=2009-02-10&sr=c&sp=r&si=YWJjZGVmZw%3d%3d&sig=dD80ihBh5jfNpymO5Hg1IdiJIEvHcJpCMiCMnN%2fRnbI%3d")
|
||||
user.FsConfig.AzBlobConfig.KeyPrefix = "somedir/subdir"
|
||||
user.FsConfig.AzBlobConfig.AccountName = ""
|
||||
user.FsConfig.AzBlobConfig.AccountKey = kms.NewEmptySecret()
|
||||
|
@ -1813,14 +1816,34 @@ func TestUserAzureBlobConfig(t *testing.T) {
|
|||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, user.FsConfig.AzBlobConfig.AccountKey)
|
||||
assert.NotNil(t, user.FsConfig.AzBlobConfig.SASURL)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
user.Password = defaultPassword
|
||||
user.ID = 0
|
||||
// sas test for add instead of update
|
||||
user.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{
|
||||
Container: user.FsConfig.AzBlobConfig.Container,
|
||||
SASURL: kms.NewPlainSecret("http://127.0.0.1/fake/sass/url"),
|
||||
}
|
||||
user, _, err = httpdtest.AddUser(user, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, user.FsConfig.AzBlobConfig.AccountKey)
|
||||
initialPayload = user.FsConfig.AzBlobConfig.SASURL.GetPayload()
|
||||
assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.SASURL.GetStatus())
|
||||
assert.NotEmpty(t, initialPayload)
|
||||
assert.Empty(t, user.FsConfig.AzBlobConfig.SASURL.GetAdditionalData())
|
||||
assert.Empty(t, user.FsConfig.AzBlobConfig.SASURL.GetKey())
|
||||
user.FsConfig.AzBlobConfig.SASURL.SetStatus(kms.SecretStatusSecretBox)
|
||||
user.FsConfig.AzBlobConfig.SASURL.SetAdditionalData("data")
|
||||
user.FsConfig.AzBlobConfig.SASURL.SetKey("fake key")
|
||||
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, kms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.SASURL.GetStatus())
|
||||
assert.Equal(t, initialPayload, user.FsConfig.AzBlobConfig.SASURL.GetPayload())
|
||||
assert.Empty(t, user.FsConfig.AzBlobConfig.SASURL.GetAdditionalData())
|
||||
assert.Empty(t, user.FsConfig.AzBlobConfig.SASURL.GetKey())
|
||||
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -7718,6 +7741,7 @@ func TestWebUserGCSMock(t *testing.T) {
|
|||
err = os.Remove(credentialsFilePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestWebUserAzureBlobMock(t *testing.T) {
|
||||
webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
|
||||
assert.NoError(t, err)
|
||||
|
@ -7763,7 +7787,6 @@ func TestWebUserAzureBlobMock(t *testing.T) {
|
|||
form.Set("az_container", user.FsConfig.AzBlobConfig.Container)
|
||||
form.Set("az_account_name", user.FsConfig.AzBlobConfig.AccountName)
|
||||
form.Set("az_account_key", user.FsConfig.AzBlobConfig.AccountKey.GetPayload())
|
||||
form.Set("az_sas_url", user.FsConfig.AzBlobConfig.SASURL)
|
||||
form.Set("az_endpoint", user.FsConfig.AzBlobConfig.Endpoint)
|
||||
form.Set("az_key_prefix", user.FsConfig.AzBlobConfig.KeyPrefix)
|
||||
form.Set("az_use_emulator", "checked")
|
||||
|
@ -7810,7 +7833,6 @@ func TestWebUserAzureBlobMock(t *testing.T) {
|
|||
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.Container, user.FsConfig.AzBlobConfig.Container)
|
||||
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.AccountName, user.FsConfig.AzBlobConfig.AccountName)
|
||||
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.Endpoint, user.FsConfig.AzBlobConfig.Endpoint)
|
||||
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.SASURL, user.FsConfig.AzBlobConfig.SASURL)
|
||||
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.KeyPrefix, user.FsConfig.AzBlobConfig.KeyPrefix)
|
||||
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.UploadPartSize, user.FsConfig.AzBlobConfig.UploadPartSize)
|
||||
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.UploadConcurrency, user.FsConfig.AzBlobConfig.UploadConcurrency)
|
||||
|
@ -7838,6 +7860,49 @@ func TestWebUserAzureBlobMock(t *testing.T) {
|
|||
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.AccountKey.GetPayload(), lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.GetPayload())
|
||||
assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.GetKey())
|
||||
assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
|
||||
// test SAS url
|
||||
user.FsConfig.AzBlobConfig.SASURL = kms.NewPlainSecret("sasurl")
|
||||
form.Set("az_account_name", "")
|
||||
form.Set("az_account_key", "")
|
||||
form.Set("az_container", "")
|
||||
form.Set("az_sas_url", user.FsConfig.AzBlobConfig.SASURL.GetPayload())
|
||||
b, contentType, _ = getMultipartFormData(form, "", "")
|
||||
req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusSeeOther, rr)
|
||||
req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
|
||||
setBearerForReq(req, apiToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
updateUser = dataprovider.User{}
|
||||
err = render.DecodeJSON(rr.Body, &updateUser)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, kms.SecretStatusSecretBox, updateUser.FsConfig.AzBlobConfig.SASURL.GetStatus())
|
||||
assert.NotEmpty(t, updateUser.FsConfig.AzBlobConfig.SASURL.GetPayload())
|
||||
assert.Empty(t, updateUser.FsConfig.AzBlobConfig.SASURL.GetKey())
|
||||
assert.Empty(t, updateUser.FsConfig.AzBlobConfig.SASURL.GetAdditionalData())
|
||||
// now check that a redacted sas url is not saved
|
||||
form.Set("az_sas_url", redactedSecret)
|
||||
b, contentType, _ = getMultipartFormData(form, "", "")
|
||||
req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
|
||||
setJWTCookieForReq(req, webToken)
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusSeeOther, rr)
|
||||
req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
|
||||
setBearerForReq(req, apiToken)
|
||||
rr = executeRequest(req)
|
||||
checkResponseCode(t, http.StatusOK, rr)
|
||||
lastUpdatedUser = dataprovider.User{}
|
||||
err = render.DecodeJSON(rr.Body, &lastUpdatedUser)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, kms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.AzBlobConfig.SASURL.GetStatus())
|
||||
assert.Equal(t, updateUser.FsConfig.AzBlobConfig.SASURL.GetPayload(), lastUpdatedUser.FsConfig.AzBlobConfig.SASURL.GetPayload())
|
||||
assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.SASURL.GetKey())
|
||||
assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.SASURL.GetAdditionalData())
|
||||
|
||||
req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
|
||||
setBearerForReq(req, apiToken)
|
||||
rr = executeRequest(req)
|
||||
|
|
|
@ -2194,8 +2194,7 @@ components:
|
|||
account_key:
|
||||
$ref: '#/components/schemas/Secret'
|
||||
sas_url:
|
||||
type: string
|
||||
description: 'Shared access signature URL, leave blank if using account/key'
|
||||
$ref: '#/components/schemas/Secret'
|
||||
endpoint:
|
||||
type: string
|
||||
description: 'optional endpoint. Default is "blob.core.windows.net". If you use the emulator the endpoint must include the protocol, for example "http://127.0.0.1:10000"'
|
||||
|
|
|
@ -739,7 +739,7 @@ func getAzureConfig(r *http.Request) (vfs.AzBlobFsConfig, error) {
|
|||
config.Container = r.Form.Get("az_container")
|
||||
config.AccountName = r.Form.Get("az_account_name")
|
||||
config.AccountKey = getSecretFromFormField(r, "az_account_key")
|
||||
config.SASURL = r.Form.Get("az_sas_url")
|
||||
config.SASURL = getSecretFromFormField(r, "az_sas_url")
|
||||
config.Endpoint = r.Form.Get("az_endpoint")
|
||||
config.KeyPrefix = r.Form.Get("az_key_prefix")
|
||||
config.AccessTier = r.Form.Get("az_access_tier")
|
||||
|
@ -1457,8 +1457,8 @@ func handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) {
|
|||
updatedUser.Password = user.Password
|
||||
}
|
||||
updateEncryptedSecrets(&updatedUser.FsConfig, user.FsConfig.S3Config.AccessSecret, user.FsConfig.AzBlobConfig.AccountKey,
|
||||
user.FsConfig.GCSConfig.Credentials, user.FsConfig.CryptConfig.Passphrase, user.FsConfig.SFTPConfig.Password,
|
||||
user.FsConfig.SFTPConfig.PrivateKey)
|
||||
user.FsConfig.AzBlobConfig.SASURL, user.FsConfig.GCSConfig.Credentials, user.FsConfig.CryptConfig.Passphrase,
|
||||
user.FsConfig.SFTPConfig.Password, user.FsConfig.SFTPConfig.PrivateKey)
|
||||
|
||||
err = dataprovider.UpdateUser(&updatedUser)
|
||||
if err == nil {
|
||||
|
@ -1569,8 +1569,8 @@ func handleWebUpdateFolderPost(w http.ResponseWriter, r *http.Request) {
|
|||
updatedFolder.FsConfig = fsConfig
|
||||
updatedFolder.FsConfig.SetEmptySecretsIfNil()
|
||||
updateEncryptedSecrets(&updatedFolder.FsConfig, folder.FsConfig.S3Config.AccessSecret, folder.FsConfig.AzBlobConfig.AccountKey,
|
||||
folder.FsConfig.GCSConfig.Credentials, folder.FsConfig.CryptConfig.Passphrase, folder.FsConfig.SFTPConfig.Password,
|
||||
folder.FsConfig.SFTPConfig.PrivateKey)
|
||||
folder.FsConfig.AzBlobConfig.SASURL, folder.FsConfig.GCSConfig.Credentials, folder.FsConfig.CryptConfig.Passphrase,
|
||||
folder.FsConfig.SFTPConfig.Password, folder.FsConfig.SFTPConfig.PrivateKey)
|
||||
|
||||
err = dataprovider.UpdateFolder(updatedFolder, folder.Users)
|
||||
if err != nil {
|
||||
|
|
|
@ -1132,8 +1132,8 @@ func compareAzBlobConfig(expected *vfs.Filesystem, actual *vfs.Filesystem) error
|
|||
if expected.AzBlobConfig.Endpoint != actual.AzBlobConfig.Endpoint {
|
||||
return errors.New("azure Blob endpoint mismatch")
|
||||
}
|
||||
if expected.AzBlobConfig.SASURL != actual.AzBlobConfig.SASURL {
|
||||
return errors.New("azure Blob SASL URL mismatch")
|
||||
if err := checkEncryptedSecret(expected.AzBlobConfig.SASURL, actual.AzBlobConfig.SASURL); err != nil {
|
||||
return fmt.Errorf("azure Blob SAS URL mismatch: %v", err)
|
||||
}
|
||||
if expected.AzBlobConfig.UploadPartSize != actual.AzBlobConfig.UploadPartSize {
|
||||
return errors.New("azure Blob upload part size mismatch")
|
||||
|
|
|
@ -282,6 +282,11 @@ func (s *Service) configurePortableSecrets() {
|
|||
if payload != "" {
|
||||
s.PortableUser.FsConfig.AzBlobConfig.AccountKey = kms.NewPlainSecret(payload)
|
||||
}
|
||||
payload = s.PortableUser.FsConfig.AzBlobConfig.SASURL.GetPayload()
|
||||
s.PortableUser.FsConfig.AzBlobConfig.SASURL = kms.NewEmptySecret()
|
||||
if payload != "" {
|
||||
s.PortableUser.FsConfig.AzBlobConfig.SASURL = kms.NewPlainSecret(payload)
|
||||
}
|
||||
case vfs.CryptedFilesystemProvider:
|
||||
payload := s.PortableUser.FsConfig.CryptConfig.Passphrase.GetPayload()
|
||||
s.PortableUser.FsConfig.CryptConfig.Passphrase = kms.NewEmptySecret()
|
||||
|
|
|
@ -7243,7 +7243,7 @@ func TestStatVFSCloudBackend(t *testing.T) {
|
|||
usePubKey := true
|
||||
u := getTestUser(usePubKey)
|
||||
u.FsConfig.Provider = vfs.AzureBlobFilesystemProvider
|
||||
u.FsConfig.AzBlobConfig.SASURL = "https://myaccount.blob.core.windows.net/sasurl"
|
||||
u.FsConfig.AzBlobConfig.SASURL = kms.NewPlainSecret("https://myaccount.blob.core.windows.net/sasurl")
|
||||
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
conn, client, err := getSftpClient(user, usePubKey)
|
||||
|
|
|
@ -161,8 +161,8 @@
|
|||
<div class="form-group row azblob">
|
||||
<label for="idAzSASURL" class="col-sm-2 col-form-label">SAS URL</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idAzSASURL" name="az_sas_url" placeholder=""
|
||||
value="{{.AzBlobConfig.SASURL}}" maxlength="255">
|
||||
<input type="password" class="form-control" id="idAzSASURL" name="az_sas_url" placeholder=""
|
||||
value="{{if .AzBlobConfig.SASURL.IsEncrypted}}{{.RedactedSecret}}{{else}}{{.AzBlobConfig.SASURL.GetPayload}}{{end}}" maxlength="1000">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row azblob">
|
||||
|
|
|
@ -75,13 +75,16 @@ func NewAzBlobFs(connectionID, localTempDir, mountPath string, config AzBlobFsCo
|
|||
if err := fs.config.AccountKey.TryDecrypt(); err != nil {
|
||||
return fs, err
|
||||
}
|
||||
if err := fs.config.SASURL.TryDecrypt(); err != nil {
|
||||
return fs, err
|
||||
}
|
||||
fs.setConfigDefaults()
|
||||
|
||||
version := version.Get()
|
||||
telemetryValue := fmt.Sprintf("SFTPGo-%v_%v", version.Version, version.CommitHash)
|
||||
|
||||
if fs.config.SASURL != "" {
|
||||
u, err := url.Parse(fs.config.SASURL)
|
||||
if fs.config.SASURL.GetPayload() != "" {
|
||||
u, err := url.Parse(fs.config.SASURL.GetPayload())
|
||||
if err != nil {
|
||||
return fs, fmt.Errorf("invalid credentials: %v", err)
|
||||
}
|
||||
|
@ -144,8 +147,8 @@ func NewAzBlobFs(connectionID, localTempDir, mountPath string, config AzBlobFsCo
|
|||
|
||||
// Name returns the name for the Fs implementation
|
||||
func (fs *AzureBlobFs) Name() string {
|
||||
if fs.config.SASURL != "" {
|
||||
return fmt.Sprintf("Azure Blob SAS URL %#v", fs.config.Container)
|
||||
if !fs.config.SASURL.IsEmpty() {
|
||||
return fmt.Sprintf("Azure Blob with SAS URL, container %#v", fs.config.Container)
|
||||
}
|
||||
return fmt.Sprintf("Azure Blob container %#v", fs.config.Container)
|
||||
}
|
||||
|
|
|
@ -37,6 +37,9 @@ func (f *Filesystem) SetEmptySecretsIfNil() {
|
|||
if f.AzBlobConfig.AccountKey == nil {
|
||||
f.AzBlobConfig.AccountKey = kms.NewEmptySecret()
|
||||
}
|
||||
if f.AzBlobConfig.SASURL == nil {
|
||||
f.AzBlobConfig.SASURL = kms.NewEmptySecret()
|
||||
}
|
||||
if f.CryptConfig.Passphrase == nil {
|
||||
f.CryptConfig.Passphrase = kms.NewEmptySecret()
|
||||
}
|
||||
|
@ -61,6 +64,9 @@ func (f *Filesystem) SetNilSecretsIfEmpty() {
|
|||
if f.AzBlobConfig.AccountKey != nil && f.AzBlobConfig.AccountKey.IsEmpty() {
|
||||
f.AzBlobConfig.AccountKey = nil
|
||||
}
|
||||
if f.AzBlobConfig.SASURL != nil && f.AzBlobConfig.SASURL.IsEmpty() {
|
||||
f.AzBlobConfig.SASURL = nil
|
||||
}
|
||||
if f.CryptConfig.Passphrase != nil && f.CryptConfig.Passphrase.IsEmpty() {
|
||||
f.CryptConfig.Passphrase = nil
|
||||
}
|
||||
|
@ -122,7 +128,7 @@ func (f *Filesystem) GetACopy() Filesystem {
|
|||
AccountName: f.AzBlobConfig.AccountName,
|
||||
AccountKey: f.AzBlobConfig.AccountKey.Clone(),
|
||||
Endpoint: f.AzBlobConfig.Endpoint,
|
||||
SASURL: f.AzBlobConfig.SASURL,
|
||||
SASURL: f.AzBlobConfig.SASURL.Clone(),
|
||||
KeyPrefix: f.AzBlobConfig.KeyPrefix,
|
||||
UploadPartSize: f.AzBlobConfig.UploadPartSize,
|
||||
UploadConcurrency: f.AzBlobConfig.UploadConcurrency,
|
||||
|
|
|
@ -108,6 +108,7 @@ func (v *BaseVirtualFolder) hideConfidentialData() {
|
|||
v.FsConfig.GCSConfig.Credentials.Hide()
|
||||
case AzureBlobFilesystemProvider:
|
||||
v.FsConfig.AzBlobConfig.AccountKey.Hide()
|
||||
v.FsConfig.AzBlobConfig.SASURL.Hide()
|
||||
case CryptedFilesystemProvider:
|
||||
v.FsConfig.CryptConfig.Passphrase.Hide()
|
||||
case SFTPFilesystemProvider:
|
||||
|
@ -139,6 +140,9 @@ func (v *BaseVirtualFolder) HasRedactedSecret() bool {
|
|||
if v.FsConfig.AzBlobConfig.AccountKey.IsRedacted() {
|
||||
return true
|
||||
}
|
||||
if v.FsConfig.AzBlobConfig.SASURL.IsRedacted() {
|
||||
return true
|
||||
}
|
||||
case CryptedFilesystemProvider:
|
||||
if v.FsConfig.CryptConfig.Passphrase.IsRedacted() {
|
||||
return true
|
||||
|
|
36
vfs/vfs.go
36
vfs/vfs.go
|
@ -342,9 +342,9 @@ type AzBlobFsConfig struct {
|
|||
// for example "http://127.0.0.1:10000"
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
// Shared access signature URL, leave blank if using account/key
|
||||
SASURL string `json:"sas_url,omitempty"`
|
||||
SASURL *kms.Secret `json:"sas_url,omitempty"`
|
||||
// KeyPrefix is similar to a chroot directory for local filesystem.
|
||||
// If specified then the SFTPGo userd will only see objects that starts
|
||||
// If specified then the SFTPGo user will only see objects that starts
|
||||
// with this prefix and so you can restrict access to a specific
|
||||
// folder. The prefix, if not empty, must not start with "/" and must
|
||||
// end with "/".
|
||||
|
@ -376,7 +376,13 @@ func (c *AzBlobFsConfig) isEqual(other *AzBlobFsConfig) bool {
|
|||
if c.Endpoint != other.Endpoint {
|
||||
return false
|
||||
}
|
||||
if c.SASURL != other.SASURL {
|
||||
if c.SASURL.IsEmpty() {
|
||||
c.SASURL = kms.NewEmptySecret()
|
||||
}
|
||||
if other.SASURL.IsEmpty() {
|
||||
other.SASURL = kms.NewEmptySecret()
|
||||
}
|
||||
if !c.SASURL.IsEqual(other.SASURL) {
|
||||
return false
|
||||
}
|
||||
if c.KeyPrefix != other.KeyPrefix {
|
||||
|
@ -411,10 +417,26 @@ func (c *AzBlobFsConfig) EncryptCredentials(additionalData string) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
if c.SASURL.IsPlain() {
|
||||
c.SASURL.SetAdditionalData(additionalData)
|
||||
if err := c.SASURL.Encrypt(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AzBlobFsConfig) checkCredentials() error {
|
||||
if c.SASURL.IsPlain() {
|
||||
_, err := url.Parse(c.SASURL.GetPayload())
|
||||
return err
|
||||
}
|
||||
if c.SASURL.IsEncrypted() && !c.SASURL.IsValid() {
|
||||
return errors.New("invalid encrypted sas_url")
|
||||
}
|
||||
if !c.SASURL.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
if c.AccountName == "" || !c.AccountKey.IsValidInput() {
|
||||
return errors.New("credentials cannot be empty or invalid")
|
||||
}
|
||||
|
@ -429,11 +451,11 @@ func (c *AzBlobFsConfig) Validate() error {
|
|||
if c.AccountKey == nil {
|
||||
c.AccountKey = kms.NewEmptySecret()
|
||||
}
|
||||
if c.SASURL != "" {
|
||||
_, err := url.Parse(c.SASURL)
|
||||
return err
|
||||
if c.SASURL == nil {
|
||||
c.SASURL = kms.NewEmptySecret()
|
||||
}
|
||||
if c.Container == "" {
|
||||
// container could be embedded within SAS URL we check this at runtime
|
||||
if c.SASURL.IsEmpty() && c.Container == "" {
|
||||
return errors.New("container cannot be empty")
|
||||
}
|
||||
if err := c.checkCredentials(); err != nil {
|
||||
|
|
Loading…
Reference in a new issue