config: use --password-command to set config file password if supplied

Before this change, rclone ignored the --password-command on the
rclone config setting except when decrypting an existing config file.

This change allows for offloading the password storage/generation into
external hardware key or other protected password storage.

Fixes #7859
This commit is contained in:
Nick Craig-Wood 2024-07-23 17:13:28 +01:00
parent c9c283533c
commit ffb2e2a6de
4 changed files with 50 additions and 3 deletions

View File

@ -1928,7 +1928,8 @@ The default is `.partial`.
This flag supplies a program which should supply the config password This flag supplies a program which should supply the config password
when run. This is an alternative to rclone prompting for the password when run. This is an alternative to rclone prompting for the password
or setting the `RCLONE_CONFIG_PASS` variable. or setting the `RCLONE_CONFIG_PASS` variable. It is also used when
setting the config password for the first time.
The argument to this should be a command with a space separated list The argument to this should be a command with a space separated list
of arguments. If one of the arguments has a space in then enclose it of arguments. If one of the arguments has a space in then enclose it

View File

@ -10,6 +10,10 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func init() {
configfile.Install()
}
func TestConfigLoad(t *testing.T) { func TestConfigLoad(t *testing.T) {
oldConfigPath := config.GetConfigPath() oldConfigPath := config.GetConfigPath()
assert.NoError(t, config.SetConfigPath("./testdata/plain.conf")) assert.NoError(t, config.SetConfigPath("./testdata/plain.conf"))
@ -17,7 +21,6 @@ func TestConfigLoad(t *testing.T) {
assert.NoError(t, config.SetConfigPath(oldConfigPath)) assert.NoError(t, config.SetConfigPath(oldConfigPath))
}() }()
config.ClearConfigPassword() config.ClearConfigPassword()
configfile.Install()
sections := config.Data().GetSectionList() sections := config.Data().GetSectionList()
var expect = []string{"RCLONE_ENCRYPT_V0", "nounc", "unc"} var expect = []string{"RCLONE_ENCRYPT_V0", "nounc", "unc"}
assert.Equal(t, expect, sections) assert.Equal(t, expect, sections)

View File

@ -310,8 +310,20 @@ func ClearConfigPassword() {
// changeConfigPassword will query the user twice // changeConfigPassword will query the user twice
// for a password. If the same password is entered // for a password. If the same password is entered
// twice the key is updated. // twice the key is updated.
//
// This will use --password-command if configured to read the password.
func changeConfigPassword() { func changeConfigPassword() {
err := SetConfigPassword(ChangePassword("NEW configuration")) pass, err := GetPasswordCommand(context.Background())
if err != nil {
fmt.Printf("Failed to read new password with --password-command: %v\n", err)
return
}
if pass == "" {
pass = ChangePassword("NEW configuration")
} else {
fmt.Printf("Read password using --password-command\n")
}
err = SetConfigPassword(pass)
if err != nil { if err != nil {
fmt.Printf("Failed to set config password: %v\n", err) fmt.Printf("Failed to set config password: %v\n", err)
return return

View File

@ -1,8 +1,10 @@
package config package config
import ( import (
"context"
"testing" "testing"
"github.com/rclone/rclone/fs"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -46,3 +48,32 @@ func TestPassword(t *testing.T) {
hashedKeyCompare(t, "abcdef", "ABCDEF", false) hashedKeyCompare(t, "abcdef", "ABCDEF", false)
} }
func TestChangeConfigPassword(t *testing.T) {
ci := fs.GetConfig(context.Background())
var err error
oldConfigPath := GetConfigPath()
assert.NoError(t, SetConfigPath("./testdata/encrypted.conf"))
defer func() {
assert.NoError(t, SetConfigPath(oldConfigPath))
ClearConfigPassword()
ci.PasswordCommand = nil
}()
// Get rid of any config password
ClearConfigPassword()
// Set correct password using --password command
ci.PasswordCommand = fs.SpaceSepList{"echo", "asdf"}
changeConfigPassword()
err = Data().Load()
require.NoError(t, err)
sections := Data().GetSectionList()
var expect = []string{"nounc", "unc"}
assert.Equal(t, expect, sections)
keys := Data().GetKeyList("nounc")
expect = []string{"type", "nounc"}
assert.Equal(t, expect, keys)
}