mirror of
				https://github.com/rclone/rclone.git
				synced 2025-10-30 23:17:59 +02:00 
			
		
		
		
	Add configuration file encryption
See #317 for details. Use `rclone config` to add/change/remove password. Tests that loads the default configuration will now fail with a better error message, and add a switch that makes it possible to disable password prompts and fail instead. Make it possible to use the "RCLONE_CONFIG_PASS" environment variable as password for configuration.
This commit is contained in:
		| @@ -436,6 +436,72 @@ Very useful for debugging. | ||||
|  | ||||
| Prints the version number | ||||
|  | ||||
| Configuration Encryption | ||||
| ------------------------ | ||||
| Your configuration file contains information for logging in to  | ||||
| your cloud services. This means that you should keep your  | ||||
| `.rclone.conf` file in a secure location. | ||||
|  | ||||
| If you are in an environment where that isn't possible, you can | ||||
| add a password to your configuration. This means that you will | ||||
| have to enter the password every time you start rclone. | ||||
|  | ||||
| To add a password to your rclone configuration, execute `rclone config`. | ||||
|  | ||||
| ``` | ||||
| >rclone config | ||||
| Current remotes: | ||||
|  | ||||
| e) Edit existing remote | ||||
| n) New remote | ||||
| d) Delete remote | ||||
| s) Set configuration password | ||||
| q) Quit config | ||||
| e/n/d/s/q> | ||||
| ``` | ||||
|  | ||||
| Go into `s`, Set configuration password: | ||||
| ``` | ||||
| e/n/d/s/q> s | ||||
| Your configuration is not encrypted. | ||||
| If you add a password, you will protect your login information to cloud services. | ||||
| a) Add Password | ||||
| q) Quit to main menu | ||||
| a/q> a | ||||
| Enter NEW configuration password: | ||||
| password> | ||||
| Confirm NEW password: | ||||
| password> | ||||
| Password set | ||||
| Your configuration is encrypted. | ||||
| c) Change Password | ||||
| u) Unencrypt configuration | ||||
| q) Quit to main menu | ||||
| c/u/q> | ||||
| ``` | ||||
|  | ||||
| Your configuration is now encrypted, and every time you start rclone | ||||
| you will now be asked for the password. In the same menu you can  | ||||
| change the password or completely remove encryption from your | ||||
| configuration. | ||||
|  | ||||
| There is no way to recover the configuration if you lose your password. | ||||
|  | ||||
| rclone uses [nacl secretbox](https://godoc.org/golang.org/x/crypto/nacl/secretbox)  | ||||
| which in term uses XSalsa20 and Poly1305 to encrypt and authenticate  | ||||
| your configuration with secret-key cryptography. | ||||
| The password is SHA-256 hashed, which produces the key for secretbox. | ||||
| The hashed password is not stored. | ||||
|  | ||||
| While this provides very good security, we do not recommend storing | ||||
| your encrypted rclone configuration in public, if it contains sensitive | ||||
| information, maybe except if you use a very strong password. | ||||
|  | ||||
| If it is safe in your environment, you can set the `RCLONE_CONFIG_PASS` | ||||
| environment variable to contain your password, in which case it will be | ||||
| used for decrypting the configuration. | ||||
|  | ||||
|  | ||||
| Developer options | ||||
| ----------------- | ||||
|  | ||||
|   | ||||
							
								
								
									
										276
									
								
								fs/config.go
									
									
									
									
									
								
							
							
						
						
									
										276
									
								
								fs/config.go
									
									
									
									
									
								
							| @@ -4,8 +4,14 @@ package fs | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"crypto/rand" | ||||
| 	"crypto/sha256" | ||||
| 	"crypto/tls" | ||||
| 	"encoding/base64" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"math" | ||||
| 	"net/http" | ||||
| @@ -16,12 +22,14 @@ import ( | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unicode/utf8" | ||||
|  | ||||
| 	"crypto/tls" | ||||
|  | ||||
| 	"github.com/Unknwon/goconfig" | ||||
| 	"github.com/klauspost/goconfig" | ||||
| 	"github.com/mreiferson/go-httpclient" | ||||
| 	"github.com/spf13/pflag" | ||||
| 	"golang.org/x/crypto/nacl/secretbox" | ||||
| 	"golang.org/x/crypto/ssh/terminal" | ||||
| 	"golang.org/x/text/unicode/norm" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -69,10 +77,15 @@ var ( | ||||
| 	dumpHeaders    = pflag.BoolP("dump-headers", "", false, "Dump HTTP headers - may contain sensitive info") | ||||
| 	dumpBodies     = pflag.BoolP("dump-bodies", "", false, "Dump HTTP headers and bodies - may contain sensitive info") | ||||
| 	skipVerify     = pflag.BoolP("no-check-certificate", "", false, "Do not verify the server SSL certificate. Insecure.") | ||||
| 	AskPassword    = pflag.BoolP("ask-password", "", true, "Allow prompt for password for encrypted configuration.") | ||||
| 	deleteBefore   = pflag.BoolP("delete-before", "", false, "When synchronizing, delete files on destination before transfering") | ||||
| 	deleteDuring   = pflag.BoolP("delete-during", "", false, "When synchronizing, delete files during transfer (default)") | ||||
| 	deleteAfter    = pflag.BoolP("delete-after", "", false, "When synchronizing, delete files on destination after transfering") | ||||
| 	bwLimit        SizeSuffix | ||||
|  | ||||
| 	// Key to use for password en/decryption. | ||||
| 	// When nil, no encryption will be used for saving. | ||||
| 	configKey []byte | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| @@ -292,13 +305,9 @@ func LoadConfig() { | ||||
|  | ||||
| 	// Load configuration file. | ||||
| 	var err error | ||||
| 	ConfigFile, err = goconfig.LoadConfigFile(ConfigPath) | ||||
| 	ConfigFile, err = loadConfigFile() | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed to load config file %v - using defaults: %v", ConfigPath, err) | ||||
| 		ConfigFile, err = goconfig.LoadConfigFile(os.DevNull) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("Failed to read null config file: %v", err) | ||||
| 		} | ||||
| 		log.Fatalf("Failed to config file \"%s\": %v", ConfigPath, err) | ||||
| 	} | ||||
|  | ||||
| 	// Load filters | ||||
| @@ -311,12 +320,186 @@ func LoadConfig() { | ||||
| 	startTokenBucket() | ||||
| } | ||||
|  | ||||
| // loadConfigFile will load a config file, and | ||||
| // automatically decrypt it. | ||||
| func loadConfigFile() (*goconfig.ConfigFile, error) { | ||||
| 	b, err := ioutil.ReadFile(ConfigPath) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed to load config file %v - using defaults: %v", ConfigPath, err) | ||||
| 		return goconfig.LoadFromData(nil) | ||||
| 	} | ||||
|  | ||||
| 	// Find first non-empty line | ||||
| 	r := bufio.NewReader(bytes.NewBuffer(b)) | ||||
| 	for { | ||||
| 		line, _, err := r.ReadLine() | ||||
| 		if err != nil { | ||||
| 			if err == io.EOF { | ||||
| 				return goconfig.LoadFromData(b) | ||||
| 			} | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		l := strings.TrimSpace(string(line)) | ||||
| 		if len(l) == 0 || strings.HasPrefix(l, ";") || strings.HasPrefix(l, "#") { | ||||
| 			continue | ||||
| 		} | ||||
| 		// First non-empty or non-comment must be ENCRYPT_V0 | ||||
| 		if l == "RCLONE_ENCRYPT_V0:" { | ||||
| 			break | ||||
| 		} | ||||
| 		if strings.HasPrefix(l, "RCLONE_ENCRYPT_V") { | ||||
| 			return nil, fmt.Errorf("Unsupported configuration encryption. Update rclone for support.") | ||||
| 		} | ||||
| 		return goconfig.LoadFromData(b) | ||||
| 	} | ||||
|  | ||||
| 	// Encrypted content is base64 encoded. | ||||
| 	dec := base64.NewDecoder(base64.StdEncoding, r) | ||||
| 	box, err := ioutil.ReadAll(dec) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("Failed to load base64 encoded data: %v", err) | ||||
| 	} | ||||
| 	if len(box) < 24+secretbox.Overhead { | ||||
| 		return nil, fmt.Errorf("Configuration data too short") | ||||
| 	} | ||||
| 	envpw := os.Getenv("RCLONE_CONFIG_PASS") | ||||
|  | ||||
| 	var out []byte | ||||
| 	for { | ||||
| 		if len(configKey) == 0 && envpw != "" { | ||||
| 			err := setPassword(envpw) | ||||
| 			if err != nil { | ||||
| 				fmt.Println("Using RCLONE_CONFIG_PASS returned:", err) | ||||
| 				envpw = "" | ||||
| 			} else { | ||||
| 				Debug(nil, "Using RCLONE_CONFIG_PASS password.") | ||||
| 			} | ||||
| 		} | ||||
| 		if len(configKey) == 0 { | ||||
| 			if !*AskPassword { | ||||
| 				return nil, fmt.Errorf("Unable to decrypt configuration and not allowed to ask for password. Set RCLONE_CONFIG_PASS to your configuration password.") | ||||
| 			} | ||||
| 			getPassword("Enter configuration password:") | ||||
| 		} | ||||
|  | ||||
| 		// Nonce is first 24 bytes of the ciphertext | ||||
| 		var nonce [24]byte | ||||
| 		copy(nonce[:], box[:24]) | ||||
| 		var key [32]byte | ||||
| 		copy(key[:], configKey[:32]) | ||||
|  | ||||
| 		// Attempt to decrypt | ||||
| 		var ok bool | ||||
| 		out, ok = secretbox.Open(nil, box[24:], &nonce, &key) | ||||
| 		if ok { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// Retry | ||||
| 		log.Println("Couldn't decrypt configuration, most likely wrong password.") | ||||
| 		configKey = nil | ||||
| 		envpw = "" | ||||
| 	} | ||||
| 	return goconfig.LoadFromData(out) | ||||
| } | ||||
|  | ||||
| // getPassword will query the user for a password the | ||||
| // first time it is required. | ||||
| func getPassword(q string) { | ||||
| 	if len(configKey) != 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	for { | ||||
| 		fmt.Println(q) | ||||
| 		fmt.Print("password>") | ||||
| 		err := setPassword(ReadPassword()) | ||||
| 		if err == nil { | ||||
| 			return | ||||
| 		} | ||||
| 		fmt.Println("Error:", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // setPassword will set the configKey to the hash of | ||||
| // the password. If the length of the password is | ||||
| // zero after trimming+normalization, an error is returned. | ||||
| func setPassword(password string) error { | ||||
| 	if !utf8.ValidString(password) { | ||||
| 		return fmt.Errorf("Password contains invalid utf8 characters") | ||||
| 	} | ||||
| 	// Remove leading+trailing whitespace | ||||
| 	password = strings.TrimSpace(password) | ||||
|  | ||||
| 	// Normalize to reduce weird variations. | ||||
| 	password = norm.NFKC.String(password) | ||||
| 	if len(password) == 0 { | ||||
| 		return fmt.Errorf("No characters in password") | ||||
| 	} | ||||
| 	// Create SHA256 has of the password | ||||
| 	sha := sha256.New() | ||||
| 	_, err := sha.Write([]byte("[" + password + "][rclone-config]")) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	configKey = sha.Sum(nil) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // SaveConfig saves configuration file. | ||||
| // if configKey has been set, the file will be encrypted. | ||||
| func SaveConfig() { | ||||
| 	err := goconfig.SaveConfigFile(ConfigFile, ConfigPath) | ||||
| 	if len(configKey) == 0 { | ||||
| 		err := goconfig.SaveConfigFile(ConfigFile, ConfigPath) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("Failed to save config file: %v", err) | ||||
| 		} | ||||
| 		err = os.Chmod(ConfigPath, 0600) | ||||
| 		if err != nil { | ||||
| 			log.Printf("Failed to set permissions on config file: %v", err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	var buf bytes.Buffer | ||||
| 	err := goconfig.SaveConfigData(ConfigFile, &buf) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to save config file: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	f, err := os.Create(ConfigPath) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to save config file: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	fmt.Fprintln(f, "# Encrypted rclone configuration File") | ||||
| 	fmt.Fprintln(f, "") | ||||
| 	fmt.Fprintln(f, "RCLONE_ENCRYPT_V0:") | ||||
|  | ||||
| 	// Generate new nonce and write it to the start of the ciphertext | ||||
| 	var nonce [24]byte | ||||
| 	n, _ := rand.Read(nonce[:]) | ||||
| 	if n != 24 { | ||||
| 		log.Fatalf("nonce short read: %d", n) | ||||
| 	} | ||||
| 	enc := base64.NewEncoder(base64.StdEncoding, f) | ||||
| 	_, err = enc.Write(nonce[:]) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to write config file: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	var key [32]byte | ||||
| 	copy(key[:], configKey[:32]) | ||||
|  | ||||
| 	b := secretbox.Seal(nil, buf.Bytes(), &nonce, &key) | ||||
| 	_, err = enc.Write(b) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to write config file: %v", err) | ||||
| 	} | ||||
| 	_ = enc.Close() | ||||
| 	err = f.Close() | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to close config file: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	err = os.Chmod(ConfigPath, 0600) | ||||
| 	if err != nil { | ||||
| 		log.Printf("Failed to set permissions on config file: %v", err) | ||||
| @@ -354,6 +537,17 @@ func ReadLine() string { | ||||
| 	return strings.TrimSpace(line) | ||||
| } | ||||
|  | ||||
| // ReadPassword reads a password | ||||
| // without echoing it to the terminal. | ||||
| func ReadPassword() string { | ||||
| 	line, err := terminal.ReadPassword(int(os.Stdin.Fd())) | ||||
| 	fmt.Println("") | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Failed to read password: %v", err) | ||||
| 	} | ||||
| 	return strings.TrimSpace(string(line)) | ||||
| } | ||||
|  | ||||
| // Command - choose one | ||||
| func Command(commands []string) byte { | ||||
| 	opts := []string{} | ||||
| @@ -547,7 +741,7 @@ func DeleteRemote(name string) { | ||||
| func EditConfig() { | ||||
| 	for { | ||||
| 		haveRemotes := len(ConfigFile.GetSectionList()) != 0 | ||||
| 		what := []string{"eEdit existing remote", "nNew remote", "dDelete remote", "qQuit config"} | ||||
| 		what := []string{"eEdit existing remote", "nNew remote", "dDelete remote", "sSet configuration password", "qQuit config"} | ||||
| 		if haveRemotes { | ||||
| 			fmt.Printf("Current remotes:\n\n") | ||||
| 			ShowRemotes() | ||||
| @@ -581,12 +775,72 @@ func EditConfig() { | ||||
| 		case 'd': | ||||
| 			name := ChooseRemote() | ||||
| 			DeleteRemote(name) | ||||
| 		case 's': | ||||
| 			SetPassword() | ||||
| 		case 'q': | ||||
| 			return | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SetPassword will allow the user to modify the current | ||||
| // configuration encryption settings. | ||||
| func SetPassword() { | ||||
| 	for { | ||||
| 		if len(configKey) > 0 { | ||||
| 			fmt.Println("Your configuration is encrypted.") | ||||
| 			what := []string{"cChange Password", "uUnencrypt configuration", "qQuit to main menu"} | ||||
| 			switch i := Command(what); i { | ||||
| 			case 'c': | ||||
| 				changePassword() | ||||
| 				SaveConfig() | ||||
| 				fmt.Println("Password changed") | ||||
| 				continue | ||||
| 			case 'u': | ||||
| 				configKey = nil | ||||
| 				SaveConfig() | ||||
| 				continue | ||||
| 			case 'q': | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 		} else { | ||||
| 			fmt.Println("Your configuration is not encrypted.") | ||||
| 			fmt.Println("If you add a password, you will protect your login information to cloud services.") | ||||
| 			what := []string{"aAdd Password", "qQuit to main menu"} | ||||
| 			switch i := Command(what); i { | ||||
| 			case 'a': | ||||
| 				changePassword() | ||||
| 				SaveConfig() | ||||
| 				fmt.Println("Password set") | ||||
| 				continue | ||||
| 			case 'q': | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // changePassword will query the user twice | ||||
| // for a password. If the same password is entered | ||||
| // twice the key is updated. | ||||
| func changePassword() { | ||||
| 	for { | ||||
| 		configKey = nil | ||||
| 		getPassword("Enter NEW configuration password:") | ||||
| 		a := configKey | ||||
| 		// re-enter password | ||||
| 		configKey = nil | ||||
| 		getPassword("Confirm NEW password:") | ||||
| 		b := configKey | ||||
| 		if bytes.Equal(a, b) { | ||||
| 			return | ||||
| 		} | ||||
| 		fmt.Println("Passwords does not match!") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Authorize is for remote authorization of headless machines. | ||||
| // | ||||
| // It expects 1 or 3 arguments | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| package fs | ||||
|  | ||||
| import "testing" | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestSizeSuffixString(t *testing.T) { | ||||
| 	for _, test := range []struct { | ||||
| @@ -73,3 +77,136 @@ func TestReveal(t *testing.T) { | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestConfigLoad(t *testing.T) { | ||||
| 	ConfigPath = "./testdata/plain.conf" | ||||
| 	configKey = nil | ||||
| 	c, err := loadConfigFile() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	sections := c.GetSectionList() | ||||
| 	var expect = []string{"RCLONE_ENCRYPT_V0", "nounc", "unc"} | ||||
| 	if !reflect.DeepEqual(sections, expect) { | ||||
| 		t.Fatalf("%v != %v", sections, expect) | ||||
| 	} | ||||
|  | ||||
| 	keys := c.GetKeyList("nounc") | ||||
| 	expect = []string{"type", "nounc"} | ||||
| 	if !reflect.DeepEqual(keys, expect) { | ||||
| 		t.Fatalf("%v != %v", keys, expect) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestConfigLoadEncrypted(t *testing.T) { | ||||
| 	var err error | ||||
| 	ConfigPath = "./testdata/encrypted.conf" | ||||
|  | ||||
| 	// Set correct password | ||||
| 	err = setPassword("asdf") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	c, err := loadConfigFile() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	sections := c.GetSectionList() | ||||
| 	var expect = []string{"nounc", "unc"} | ||||
| 	if !reflect.DeepEqual(sections, expect) { | ||||
| 		t.Fatalf("%v != %v", sections, expect) | ||||
| 	} | ||||
|  | ||||
| 	keys := c.GetKeyList("nounc") | ||||
| 	expect = []string{"type", "nounc"} | ||||
| 	if !reflect.DeepEqual(keys, expect) { | ||||
| 		t.Fatalf("%v != %v", keys, expect) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestConfigLoadEncryptedFailures(t *testing.T) { | ||||
| 	var err error | ||||
|  | ||||
| 	// This file should be too short to be decoded. | ||||
| 	ConfigPath = "./testdata/enc-short.conf" | ||||
| 	_, err = loadConfigFile() | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error") | ||||
| 	} | ||||
| 	t.Log("Correctly got:", err) | ||||
|  | ||||
| 	// This file contains invalid base64 characters. | ||||
| 	ConfigPath = "./testdata/enc-invalid.conf" | ||||
| 	_, err = loadConfigFile() | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error") | ||||
| 	} | ||||
| 	t.Log("Correctly got:", err) | ||||
|  | ||||
| 	// This file contains invalid base64 characters. | ||||
| 	ConfigPath = "./testdata/enc-too-new.conf" | ||||
| 	_, err = loadConfigFile() | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error") | ||||
| 	} | ||||
| 	t.Log("Correctly got:", err) | ||||
|  | ||||
| 	// This file contains invalid base64 characters. | ||||
| 	ConfigPath = "./testdata/filenotfound.conf" | ||||
| 	c, err := loadConfigFile() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if len(c.GetSectionList()) != 0 { | ||||
| 		t.Fatalf("Expected 0-length section, got %d entries", len(c.GetSectionList())) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPassword(t *testing.T) { | ||||
| 	var err error | ||||
| 	// Empty password should give error | ||||
| 	err = setPassword("  \t  ") | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error") | ||||
| 	} | ||||
|  | ||||
| 	// Test invalid utf8 sequence | ||||
| 	err = setPassword(string([]byte{0xff, 0xfe, 0xfd}) + "abc") | ||||
| 	if err == nil { | ||||
| 		t.Fatal("expected error") | ||||
| 	} | ||||
|  | ||||
| 	// Simple check of wrong passwords | ||||
| 	hashedKeyCompare(t, "mis", "match", false) | ||||
|  | ||||
| 	// Check that passwords match with trimmed whitespace | ||||
| 	hashedKeyCompare(t, "   abcdef   \t", "abcdef", true) | ||||
|  | ||||
| 	// Check that passwords match after unicode normalization | ||||
| 	hashedKeyCompare(t, "ff\u0041\u030A", "ffÅ", true) | ||||
|  | ||||
| 	// Check that passwords preserves case | ||||
| 	hashedKeyCompare(t, "abcdef", "ABCDEF", false) | ||||
|  | ||||
| } | ||||
|  | ||||
| func hashedKeyCompare(t *testing.T, a, b string, shouldMatch bool) { | ||||
| 	err := setPassword(a) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	k1 := configKey | ||||
|  | ||||
| 	err = setPassword(b) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	k2 := configKey | ||||
| 	matches := bytes.Equal(k1, k2) | ||||
| 	if shouldMatch && !matches { | ||||
| 		t.Fatalf("%v != %v", k1, k2) | ||||
| 	} | ||||
| 	if !shouldMatch && matches { | ||||
| 		t.Fatalf("%v == %v", k1, k2) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -94,6 +94,10 @@ func newRun() *Run { | ||||
| 		mkdir:  make(map[string]bool), | ||||
| 	} | ||||
|  | ||||
| 	// Never ask for passwords, fail instead. | ||||
| 	// If your local config is encrypted set environment variable | ||||
| 	// "RCLONE_CONFIG_PASS=hunter2" (or your password) | ||||
| 	*fs.AskPassword = false | ||||
| 	fs.LoadConfig() | ||||
| 	fs.Config.Verbose = *Verbose | ||||
| 	fs.Config.Quiet = !*Verbose | ||||
|   | ||||
							
								
								
									
										4
									
								
								fs/testdata/enc-invalid.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								fs/testdata/enc-invalid.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| # Encrypted rclone configuration File | ||||
|  | ||||
| RCLONE_ENCRYPT_V0: | ||||
| b5Uk6mE3cUn5Wb8xiWYnVBAxXUirAaEG1PO/GIDiO9274AOæøå+Yj790BwJA4d2y7lNkmHt4nJwIsoueFvUYmm7RDyzER8IA3XOCrjzl3OUcczZqcplk5JfBdhxMZpt1aGYWUdle1IgO/kAFne6sLD6IuxPySEb | ||||
							
								
								
									
										4
									
								
								fs/testdata/enc-short.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								fs/testdata/enc-short.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| # Encrypted rclone configuration File | ||||
|  | ||||
| RCLONE_ENCRYPT_V0: | ||||
| b5Uk6mE3cUn5Wb8xi | ||||
							
								
								
									
										4
									
								
								fs/testdata/enc-too-new.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								fs/testdata/enc-too-new.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| # Encrypted rclone configuration File | ||||
|  | ||||
| RCLONE_ENCRYPT_V1: | ||||
| b5Uk6mE3cUn5Wb8xiWYnVBAxXUirAaEG1PO/GIDiO9274AO+Yj790BwJA4d2y7lNkmHt4nJwIsoueFvUYmm7RDyzER8IA3XOCrjzl3OUcczZqcplk5JfBdhxMZpt1aGYWUdle1IgO/kAFne6sLD6IuxPySEb | ||||
							
								
								
									
										4
									
								
								fs/testdata/encrypted.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								fs/testdata/encrypted.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| # Encrypted rclone configuration File | ||||
|  | ||||
| RCLONE_ENCRYPT_V0: | ||||
| b5Uk6mE3cUn5Wb8xiWYnVBAxXUirAaEG1PO/GIDiO9274AO+Yj790BwJA4d2y7lNkmHt4nJwIsoueFvUYmm7RDyzER8IA3XOCrjzl3OUcczZqcplk5JfBdhxMZpt1aGYWUdle1IgO/kAFne6sLD6IuxPySEb | ||||
							
								
								
									
										12
									
								
								fs/testdata/plain.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								fs/testdata/plain.conf
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| [RCLONE_ENCRYPT_V0] | ||||
| type = local | ||||
| nounc = true | ||||
|  | ||||
| [nounc] | ||||
| type = local | ||||
| nounc = true | ||||
|  | ||||
|  | ||||
| [unc] | ||||
| type = local | ||||
| nounc = false | ||||
| @@ -50,6 +50,11 @@ func init() { | ||||
| // TestInit tests basic intitialisation | ||||
| func TestInit(t *testing.T) { | ||||
| 	var err error | ||||
|  | ||||
| 	// Never ask for passwords, fail instead. | ||||
| 	// If your local config is encrypted set environment variable | ||||
| 	// "RCLONE_CONFIG_PASS=hunter2" (or your password) | ||||
| 	*fs.AskPassword = false | ||||
| 	fs.LoadConfig() | ||||
| 	fs.Config.Verbose = *verbose | ||||
| 	fs.Config.Quiet = !*verbose | ||||
|   | ||||
		Reference in New Issue
	
	Block a user