config encryption: set, remove and check to manage config file encryption #7859
This commit is contained in:
parent
ffb2e2a6de
commit
2d1c2b1f76
6 changed files with 194 additions and 10 deletions
|
@ -36,6 +36,7 @@ func init() {
|
||||||
configCommand.AddCommand(configReconnectCommand)
|
configCommand.AddCommand(configReconnectCommand)
|
||||||
configCommand.AddCommand(configDisconnectCommand)
|
configCommand.AddCommand(configDisconnectCommand)
|
||||||
configCommand.AddCommand(configUserInfoCommand)
|
configCommand.AddCommand(configUserInfoCommand)
|
||||||
|
configCommand.AddCommand(configEncryptionCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
var configCommand = &cobra.Command{
|
var configCommand = &cobra.Command{
|
||||||
|
@ -518,3 +519,91 @@ system.
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
configEncryptionCommand.AddCommand(configEncryptionSetCommand)
|
||||||
|
configEncryptionCommand.AddCommand(configEncryptionRemoveCommand)
|
||||||
|
configEncryptionCommand.AddCommand(configEncryptionCheckCommand)
|
||||||
|
}
|
||||||
|
|
||||||
|
var configEncryptionCommand = &cobra.Command{
|
||||||
|
Use: "encryption",
|
||||||
|
Short: `set, remove and check the encryption for the config file`,
|
||||||
|
Long: `This command sets, clears and checks the encryption for the config file using
|
||||||
|
the subcommands below.
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
var configEncryptionSetCommand = &cobra.Command{
|
||||||
|
Use: "set",
|
||||||
|
Short: `Set or change the config file encryption password`,
|
||||||
|
Long: strings.ReplaceAll(`This command sets or changes the config file encryption password.
|
||||||
|
|
||||||
|
If there was no config password set then it sets a new one, otherwise
|
||||||
|
it changes the existing config password.
|
||||||
|
|
||||||
|
Note that if you are changing an encryption password using
|
||||||
|
|--password-command| then this will be called once to decrypt the
|
||||||
|
config using the old password and then again to read the new
|
||||||
|
password to re-encrypt the config.
|
||||||
|
|
||||||
|
When |--password-command| is called to change the password then the
|
||||||
|
environment variable |RCLONE_PASSWORD_CHANGE=1| will be set. So if
|
||||||
|
changing passwords programatically you can use the environment
|
||||||
|
variable to distinguish which password you must supply.
|
||||||
|
|
||||||
|
Alternatively you can remove the password first (with |rclone config
|
||||||
|
encryption remove|), then set it again with this command which may be
|
||||||
|
easier if you don't mind the unecrypted config file being on the disk
|
||||||
|
briefly.
|
||||||
|
`, "|", "`"),
|
||||||
|
RunE: func(command *cobra.Command, args []string) error {
|
||||||
|
cmd.CheckArgs(0, 0, command, args)
|
||||||
|
config.LoadedData()
|
||||||
|
config.ChangeConfigPasswordAndSave()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var configEncryptionRemoveCommand = &cobra.Command{
|
||||||
|
Use: "remove",
|
||||||
|
Short: `Remove the config file encryption password`,
|
||||||
|
Long: strings.ReplaceAll(`Remove the config file encryption password
|
||||||
|
|
||||||
|
This removes the config file encryption, returning it to un-encrypted.
|
||||||
|
|
||||||
|
If |--password-command| is in use, this will be called to supply the old config
|
||||||
|
password.
|
||||||
|
|
||||||
|
If the config was not encrypted then no error will be returned and
|
||||||
|
this command will do nothing.
|
||||||
|
`, "|", "`"),
|
||||||
|
RunE: func(command *cobra.Command, args []string) error {
|
||||||
|
cmd.CheckArgs(0, 0, command, args)
|
||||||
|
config.LoadedData()
|
||||||
|
config.RemoveConfigPasswordAndSave()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var configEncryptionCheckCommand = &cobra.Command{
|
||||||
|
Use: "check",
|
||||||
|
Short: `Check that the config file is encrypted`,
|
||||||
|
Long: strings.ReplaceAll(`This checks the config file is encrypted and that you can decrypt it.
|
||||||
|
|
||||||
|
It will attempt to decrypt the config using the password you supply.
|
||||||
|
|
||||||
|
If decryption fails it will return a non-zero exit code if using
|
||||||
|
|--password-command|, otherwise it will prompt again for the password.
|
||||||
|
|
||||||
|
If the config file is not encrypted it will return a non zero exit code.
|
||||||
|
`, "|", "`"),
|
||||||
|
RunE: func(command *cobra.Command, args []string) error {
|
||||||
|
cmd.CheckArgs(0, 0, command, args)
|
||||||
|
config.LoadedData()
|
||||||
|
if !config.IsEncrypted() {
|
||||||
|
return errors.New("config file is NOT encrypted")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -1924,7 +1924,7 @@ Suffix length limit is 16 characters.
|
||||||
|
|
||||||
The default is `.partial`.
|
The default is `.partial`.
|
||||||
|
|
||||||
### --password-command SpaceSepList ###
|
### --password-command SpaceSepList {#password-command}
|
||||||
|
|
||||||
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
|
||||||
|
@ -1943,6 +1943,11 @@ Eg
|
||||||
--password-command 'echo "hello with space"'
|
--password-command 'echo "hello with space"'
|
||||||
--password-command 'echo "hello with ""quotes"" and space"'
|
--password-command 'echo "hello with ""quotes"" and space"'
|
||||||
|
|
||||||
|
Note that when changing the configuration password the environment
|
||||||
|
variable `RCLONE_PASSWORD_CHANGE=1` will be set. This can be used to
|
||||||
|
distinguish initial decryption of the config file from the new
|
||||||
|
password.
|
||||||
|
|
||||||
See the [Configuration Encryption](#configuration-encryption) for more info.
|
See the [Configuration Encryption](#configuration-encryption) for more info.
|
||||||
|
|
||||||
See a [Windows PowerShell example on the Wiki](https://github.com/rclone/rclone/wiki/Windows-Powershell-use-rclone-password-command-for-Config-file-password).
|
See a [Windows PowerShell example on the Wiki](https://github.com/rclone/rclone/wiki/Windows-Powershell-use-rclone-password-command-for-Config-file-password).
|
||||||
|
@ -2546,6 +2551,12 @@ encryption from your configuration.
|
||||||
|
|
||||||
There is no way to recover the configuration if you lose your password.
|
There is no way to recover the configuration if you lose your password.
|
||||||
|
|
||||||
|
You can also use
|
||||||
|
|
||||||
|
- [rclone config encryption set](/commands/rclone_config_encryption_set/) to set the config encryption directly
|
||||||
|
- [rclone config encryption remove](/commands/rclone_config_encryption_remove/) to remove it
|
||||||
|
- [rclone config encryption check](/commands/rclone_config_encryption_check/) to check that it is encrypted properly.
|
||||||
|
|
||||||
rclone uses [nacl secretbox](https://godoc.org/golang.org/x/crypto/nacl/secretbox)
|
rclone uses [nacl secretbox](https://godoc.org/golang.org/x/crypto/nacl/secretbox)
|
||||||
which in turn uses XSalsa20 and Poly1305 to encrypt and authenticate
|
which in turn uses XSalsa20 and Poly1305 to encrypt and authenticate
|
||||||
your configuration with secret-key cryptography.
|
your configuration with secret-key cryptography.
|
||||||
|
@ -2578,7 +2589,7 @@ An alternate means of supplying the password is to provide a script
|
||||||
which will retrieve the password and print on standard output. This
|
which will retrieve the password and print on standard output. This
|
||||||
script should have a fully specified path name and not rely on any
|
script should have a fully specified path name and not rely on any
|
||||||
environment variables. The script is supplied either via
|
environment variables. The script is supplied either via
|
||||||
`--password-command="..."` command line argument or via the
|
[`--password-command="..."`](#password-command) command line argument or via the
|
||||||
`RCLONE_PASSWORD_COMMAND` environment variable.
|
`RCLONE_PASSWORD_COMMAND` environment variable.
|
||||||
|
|
||||||
One useful example of this is using the `passwordstore` application
|
One useful example of this is using the `passwordstore` application
|
||||||
|
|
|
@ -41,6 +41,11 @@ var (
|
||||||
PassConfigKeyForDaemonization = false
|
PassConfigKeyForDaemonization = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsEncrypted returns true if the config file is encrypted
|
||||||
|
func IsEncrypted() bool {
|
||||||
|
return len(configKey) > 0
|
||||||
|
}
|
||||||
|
|
||||||
// Decrypt will automatically decrypt a reader
|
// Decrypt will automatically decrypt a reader
|
||||||
func Decrypt(b io.ReadSeeker) (io.Reader, error) {
|
func Decrypt(b io.ReadSeeker) (io.Reader, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
@ -313,6 +318,11 @@ func ClearConfigPassword() {
|
||||||
//
|
//
|
||||||
// This will use --password-command if configured to read the password.
|
// This will use --password-command if configured to read the password.
|
||||||
func changeConfigPassword() {
|
func changeConfigPassword() {
|
||||||
|
// Set RCLONE_PASSWORD_CHANGE to "1" when calling the --password-command tool
|
||||||
|
_ = os.Setenv("RCLONE_PASSWORD_CHANGE", "1")
|
||||||
|
defer func() {
|
||||||
|
_ = os.Unsetenv("RCLONE_PASSWORD_CHANGE")
|
||||||
|
}()
|
||||||
pass, err := GetPasswordCommand(context.Background())
|
pass, err := GetPasswordCommand(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to read new password with --password-command: %v\n", err)
|
fmt.Printf("Failed to read new password with --password-command: %v\n", err)
|
||||||
|
@ -329,3 +339,22 @@ func changeConfigPassword() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChangeConfigPasswordAndSave will query the user twice
|
||||||
|
// for a password. If the same password is entered
|
||||||
|
// twice the key is updated.
|
||||||
|
//
|
||||||
|
// This will use --password-command if configured to read the password.
|
||||||
|
//
|
||||||
|
// It will then save the config
|
||||||
|
func ChangeConfigPasswordAndSave() {
|
||||||
|
changeConfigPassword()
|
||||||
|
SaveConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveConfigPasswordAndSave will clear the config password and save
|
||||||
|
// the unencrypted config file.
|
||||||
|
func RemoveConfigPasswordAndSave() {
|
||||||
|
configKey = nil
|
||||||
|
SaveConfig()
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
|
@ -64,8 +66,33 @@ func TestChangeConfigPassword(t *testing.T) {
|
||||||
// Get rid of any config password
|
// Get rid of any config password
|
||||||
ClearConfigPassword()
|
ClearConfigPassword()
|
||||||
|
|
||||||
// Set correct password using --password command
|
// Return the password, checking the state of the environment variable
|
||||||
ci.PasswordCommand = fs.SpaceSepList{"echo", "asdf"}
|
checkCode := `
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
v := os.Getenv("RCLONE_PASSWORD_CHANGE")
|
||||||
|
if v == "" {
|
||||||
|
log.Fatal("Env var not found")
|
||||||
|
} else if v != "1" {
|
||||||
|
log.Fatal("Env var wrong value")
|
||||||
|
} else {
|
||||||
|
fmt.Println("asdf")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
dir := t.TempDir()
|
||||||
|
code := filepath.Join(dir, "file.go")
|
||||||
|
require.NoError(t, os.WriteFile(code, []byte(checkCode), 0777))
|
||||||
|
|
||||||
|
// Set correct password using --password-command
|
||||||
|
ci.PasswordCommand = fs.SpaceSepList{"go", "run", code}
|
||||||
changeConfigPassword()
|
changeConfigPassword()
|
||||||
err = Data().Load()
|
err = Data().Load()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -6,6 +6,8 @@ package config_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/rclone/rclone/fs"
|
"github.com/rclone/rclone/fs"
|
||||||
|
@ -24,8 +26,10 @@ func TestConfigLoadEncrypted(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Set correct password
|
// Set correct password
|
||||||
|
assert.False(t, config.IsEncrypted())
|
||||||
err = config.SetConfigPassword("asdf")
|
err = config.SetConfigPassword("asdf")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
assert.True(t, config.IsEncrypted())
|
||||||
err = config.Data().Load()
|
err = config.Data().Load()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
sections := config.Data().GetSectionList()
|
sections := config.Data().GetSectionList()
|
||||||
|
@ -138,4 +142,31 @@ func TestGetPasswordCommand(t *testing.T) {
|
||||||
ci.PasswordCommand = fs.SpaceSepList{"XXX non-existent command XXX", ""}
|
ci.PasswordCommand = fs.SpaceSepList{"XXX non-existent command XXX", ""}
|
||||||
_, err = config.GetPasswordCommand(ctx)
|
_, err = config.GetPasswordCommand(ctx)
|
||||||
assert.ErrorContains(t, err, "not found")
|
assert.ErrorContains(t, err, "not found")
|
||||||
|
|
||||||
|
// Check the state of the environment variable in --password-command
|
||||||
|
checkCode := `
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if _, found := os.LookupEnv("RCLONE_PASSWORD_CHANGE"); found {
|
||||||
|
fmt.Println("Env var set")
|
||||||
|
} else {
|
||||||
|
fmt.Println("OK")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
dir := t.TempDir()
|
||||||
|
code := filepath.Join(dir, "file.go")
|
||||||
|
require.NoError(t, os.WriteFile(code, []byte(checkCode), 0777))
|
||||||
|
|
||||||
|
// Check the environment variable unset when called directly
|
||||||
|
ci.PasswordCommand = fs.SpaceSepList{"go", "run", code}
|
||||||
|
pass, err = config.GetPasswordCommand(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "OK", pass)
|
||||||
}
|
}
|
||||||
|
|
|
@ -797,13 +797,11 @@ func SetPassword() {
|
||||||
what := []string{"cChange Password", "uUnencrypt configuration", "qQuit to main menu"}
|
what := []string{"cChange Password", "uUnencrypt configuration", "qQuit to main menu"}
|
||||||
switch i := Command(what); i {
|
switch i := Command(what); i {
|
||||||
case 'c':
|
case 'c':
|
||||||
changeConfigPassword()
|
ChangeConfigPasswordAndSave()
|
||||||
SaveConfig()
|
|
||||||
fmt.Println("Password changed")
|
fmt.Println("Password changed")
|
||||||
continue
|
continue
|
||||||
case 'u':
|
case 'u':
|
||||||
configKey = nil
|
RemoveConfigPasswordAndSave()
|
||||||
SaveConfig()
|
|
||||||
continue
|
continue
|
||||||
case 'q':
|
case 'q':
|
||||||
return
|
return
|
||||||
|
@ -815,8 +813,7 @@ func SetPassword() {
|
||||||
what := []string{"aAdd Password", "qQuit to main menu"}
|
what := []string{"aAdd Password", "qQuit to main menu"}
|
||||||
switch i := Command(what); i {
|
switch i := Command(what); i {
|
||||||
case 'a':
|
case 'a':
|
||||||
changeConfigPassword()
|
ChangeConfigPasswordAndSave()
|
||||||
SaveConfig()
|
|
||||||
fmt.Println("Password set")
|
fmt.Println("Password set")
|
||||||
continue
|
continue
|
||||||
case 'q':
|
case 'q':
|
||||||
|
|
Loading…
Reference in a new issue