diff --git a/fs/config/crypt.go b/fs/config/crypt.go index 158eaa220..1eab45c1d 100644 --- a/fs/config/crypt.go +++ b/fs/config/crypt.go @@ -85,7 +85,9 @@ func Decrypt(b io.ReadSeeker) (io.Reader, error) { 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) if err != nil { return nil, err @@ -130,7 +132,9 @@ func Decrypt(b io.ReadSeeker) (io.Reader, error) { var out []byte 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) obscuredKey, err := os.ReadFile(envKeyFile) if err != nil { diff --git a/fs/config/crypt_test.go b/fs/config/crypt_test.go index 73c3bf6f6..a5d27aea1 100644 --- a/fs/config/crypt_test.go +++ b/fs/config/crypt_test.go @@ -70,6 +70,48 @@ func TestConfigLoadEncryptedWithValidPassCommand(t *testing.T) { 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) { ctx := context.Background() ci := fs.GetConfig(ctx)