fs/config: only run --password-command once when using --daemon
Decrypting the config with --daemon runs --password-command twice, which means two authentications when the command needs one, such as a hardware key touch for `pass show`. SetConfigPassword saves the obscured key to the temp file named by _RCLONE_CONFIG_KEY_FILE so the daemon process can pick it up, but the process that wrote it then read and deleted that file itself before daemonizing. The daemon started with the variable pointing at a file that was already gone, found no key, and ran the password command again. Skip acquiring a password when _RCLONE_CONFIG_KEY_FILE is set, as the PassConfigKeyForDaemonization documentation already describes, and only consume the key file in a process that has no key of its own. The parent then leaves the key for the daemon, and the daemon uses it. Fixes #7341
This commit is contained in:
+6
-2
@@ -85,7 +85,9 @@ func Decrypt(b io.ReadSeeker) (io.Reader, error) {
|
|||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(configKey) == 0 {
|
// A key file named by _RCLONE_CONFIG_KEY_FILE is a key handed over by a
|
||||||
|
// parent process, so the password must not be asked for again here.
|
||||||
|
if len(configKey) == 0 && os.Getenv("_RCLONE_CONFIG_KEY_FILE") == "" {
|
||||||
pass, err := GetPasswordCommand(ctx)
|
pass, err := GetPasswordCommand(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -130,7 +132,9 @@ func Decrypt(b io.ReadSeeker) (io.Reader, error) {
|
|||||||
|
|
||||||
var out []byte
|
var out []byte
|
||||||
for {
|
for {
|
||||||
if envKeyFile := os.Getenv("_RCLONE_CONFIG_KEY_FILE"); len(envKeyFile) > 0 {
|
// Only a process without a key of its own consumes the key file, so
|
||||||
|
// that a process which wrote one for its child leaves it in place.
|
||||||
|
if envKeyFile := os.Getenv("_RCLONE_CONFIG_KEY_FILE"); len(configKey) == 0 && len(envKeyFile) > 0 {
|
||||||
fs.Debugf(nil, "attempting to obtain configKey from temp file %s", envKeyFile)
|
fs.Debugf(nil, "attempting to obtain configKey from temp file %s", envKeyFile)
|
||||||
obscuredKey, err := os.ReadFile(envKeyFile)
|
obscuredKey, err := os.ReadFile(envKeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -70,6 +70,48 @@ func TestConfigLoadEncryptedWithValidPassCommand(t *testing.T) {
|
|||||||
assert.Equal(t, expect, keys)
|
assert.Equal(t, expect, keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestConfigLoadEncryptedWithPassCommandAndDaemon checks the configKey handoff
|
||||||
|
// that --daemon relies on: the parent process leaves the obscured key in the
|
||||||
|
// temp file named by _RCLONE_CONFIG_KEY_FILE, and the daemon process uses that
|
||||||
|
// instead of running --password-command a second time.
|
||||||
|
func TestConfigLoadEncryptedWithPassCommandAndDaemon(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
ci := fs.GetConfig(ctx)
|
||||||
|
oldConfigPath := config.GetConfigPath()
|
||||||
|
oldConfig := *ci
|
||||||
|
require.NoError(t, config.SetConfigPath("./testdata/encrypted.conf"))
|
||||||
|
config.PassConfigKeyForDaemonization = true
|
||||||
|
t.Setenv("_RCLONE_CONFIG_KEY_FILE", "")
|
||||||
|
defer func() {
|
||||||
|
assert.NoError(t, config.SetConfigPath(oldConfigPath))
|
||||||
|
config.ClearConfigPassword()
|
||||||
|
config.PassConfigKeyForDaemonization = false
|
||||||
|
*ci = oldConfig
|
||||||
|
ci.PasswordCommand = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
// The parent reads the password and saves the key for the daemon.
|
||||||
|
ci.PasswordCommand = fs.SpaceSepList{"echo", "asdf"}
|
||||||
|
config.ClearConfigPassword()
|
||||||
|
require.NoError(t, config.Data().Load())
|
||||||
|
|
||||||
|
keyFile := os.Getenv("_RCLONE_CONFIG_KEY_FILE")
|
||||||
|
require.NotEmpty(t, keyFile)
|
||||||
|
_, err := os.Stat(keyFile)
|
||||||
|
require.NoError(t, err, "parent must leave the key file for the daemon")
|
||||||
|
|
||||||
|
// The daemon inherits the environment but not the key. Running
|
||||||
|
// --password-command again would yield this wrong password.
|
||||||
|
config.ClearConfigPassword()
|
||||||
|
ci.PasswordCommand = fs.SpaceSepList{"echo", "not-the-password"}
|
||||||
|
require.NoError(t, config.Data().Load())
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"nounc", "unc"}, config.Data().GetSectionList())
|
||||||
|
|
||||||
|
_, err = os.Stat(keyFile)
|
||||||
|
assert.True(t, os.IsNotExist(err), "daemon must delete the key file once used")
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfigLoadEncryptedWithInvalidPassCommand(t *testing.T) {
|
func TestConfigLoadEncryptedWithInvalidPassCommand(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ci := fs.GetConfig(ctx)
|
ci := fs.GetConfig(ctx)
|
||||||
|
|||||||
Reference in New Issue
Block a user