diff --git a/cmd/restic/cmd_init.go b/cmd/restic/cmd_init.go index 7154279e8..16efd1db0 100644 --- a/cmd/restic/cmd_init.go +++ b/cmd/restic/cmd_init.go @@ -36,6 +36,7 @@ type InitOptions struct { secondaryRepoOptions CopyChunkerParameters bool RepositoryVersion string + InsecurePassword bool } var initOptions InitOptions @@ -47,6 +48,7 @@ func init() { initSecondaryRepoOptions(f, &initOptions.secondaryRepoOptions, "secondary", "to copy chunker parameters from") f.BoolVar(&initOptions.CopyChunkerParameters, "copy-chunker-params", false, "copy chunker parameters from the secondary repository (useful with the copy command)") f.StringVar(&initOptions.RepositoryVersion, "repository-version", "stable", "repository format version to use, allowed values are a format version, 'latest' and 'stable'") + f.BoolVar(&initOptions.InsecurePassword, "insecure-password", false, "allow an empty password (feel beeing warned)") } func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []string) error { @@ -82,7 +84,8 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args [] gopts.password, err = ReadPasswordTwice(gopts, "enter password for new repository: ", - "enter password again: ") + "enter password again: ", + opts.InsecurePassword) if err != nil { return err } diff --git a/cmd/restic/cmd_key_add.go b/cmd/restic/cmd_key_add.go index 43a38f4eb..b94a6a0e7 100644 --- a/cmd/restic/cmd_key_add.go +++ b/cmd/restic/cmd_key_add.go @@ -29,9 +29,10 @@ Exit status is 0 if the command is successful, and non-zero if there was any err } type KeyAddOptions struct { - NewPasswordFile string - Username string - Hostname string + NewPasswordFile string + InsecurePassword bool + Username string + Hostname string } var keyAddOpts KeyAddOptions @@ -41,6 +42,7 @@ func init() { flags := cmdKeyAdd.Flags() flags.StringVarP(&keyAddOpts.NewPasswordFile, "new-password-file", "", "", "`file` from which to read the new password") + flags.BoolVar(&keyAddOpts.InsecurePassword, "insecure-password", false, "allow an empty password (feel beeing warned)") flags.StringVarP(&keyAddOpts.Username, "user", "", "", "the username for new key") flags.StringVarP(&keyAddOpts.Hostname, "host", "", "", "the hostname for new key") } @@ -65,7 +67,7 @@ func runKeyAdd(ctx context.Context, gopts GlobalOptions, opts KeyAddOptions, arg } func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyAddOptions) error { - pw, err := getNewPassword(gopts, opts.NewPasswordFile) + pw, err := getNewPassword(gopts, opts.NewPasswordFile, opts.InsecurePassword) if err != nil { return err } @@ -88,7 +90,7 @@ func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOption // testKeyNewPassword is used to set a new password during integration testing. var testKeyNewPassword string -func getNewPassword(gopts GlobalOptions, newPasswordFile string) (string, error) { +func getNewPassword(gopts GlobalOptions, newPasswordFile string, allowInsecurePassword bool) (string, error) { if testKeyNewPassword != "" { return testKeyNewPassword, nil } @@ -104,7 +106,8 @@ func getNewPassword(gopts GlobalOptions, newPasswordFile string) (string, error) return ReadPasswordTwice(newopts, "enter new password: ", - "enter password again: ") + "enter password again: ", + allowInsecurePassword) } func loadPasswordFromFile(pwdFile string) (string, error) { diff --git a/cmd/restic/cmd_key_passwd.go b/cmd/restic/cmd_key_passwd.go index cb916274c..30f0c2638 100644 --- a/cmd/restic/cmd_key_passwd.go +++ b/cmd/restic/cmd_key_passwd.go @@ -38,6 +38,7 @@ func init() { flags := cmdKeyPasswd.Flags() flags.StringVarP(&keyPasswdOpts.NewPasswordFile, "new-password-file", "", "", "`file` from which to read the new password") + flags.BoolVar(&keyPasswdOpts.InsecurePassword, "insecure-password", false, "allow an empty password (feel beeing warned)") flags.StringVarP(&keyPasswdOpts.Username, "user", "", "", "the username for new key") flags.StringVarP(&keyPasswdOpts.Hostname, "host", "", "", "the hostname for new key") } @@ -62,7 +63,7 @@ func runKeyPasswd(ctx context.Context, gopts GlobalOptions, opts KeyPasswdOption } func changePassword(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyPasswdOptions) error { - pw, err := getNewPassword(gopts, opts.NewPasswordFile) + pw, err := getNewPassword(gopts, opts.NewPasswordFile, opts.InsecurePassword) if err != nil { return err } diff --git a/cmd/restic/global.go b/cmd/restic/global.go index cc47496f3..bb425a9ff 100644 --- a/cmd/restic/global.go +++ b/cmd/restic/global.go @@ -313,7 +313,11 @@ func resolvePassword(opts GlobalOptions, envStr string) (string, error) { if errors.Is(err, os.ErrNotExist) { return "", errors.Fatalf("%s does not exist", opts.PasswordFile) } - return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile") + pwd := strings.TrimSpace(string(s)) + if pwd == "" { + pwd = emptyPassword + } + return pwd, errors.Wrap(err, "Readfile") } if pwd := os.Getenv(envStr); pwd != "" { @@ -348,9 +352,11 @@ func readPasswordTerminal(in *os.File, out io.Writer, prompt string) (password s return password, nil } +const emptyPassword = "\xff" + // ReadPassword reads the password from a password file, the environment // variable RESTIC_PASSWORD or prompts the user. -func ReadPassword(opts GlobalOptions, prompt string) (string, error) { +func ReadPassword(opts GlobalOptions, prompt string, allowInsecurePassword bool) (string, error) { if opts.password != "" { return opts.password, nil } @@ -372,7 +378,12 @@ func ReadPassword(opts GlobalOptions, prompt string) (string, error) { } if len(password) == 0 { - return "", errors.Fatal("an empty password is not a password") + if allowInsecurePassword { + Warnf("using an empty password is bad security practice\n") + return emptyPassword, nil + } else { + return "", errors.Fatal("an empty password is not a password") + } } return password, nil @@ -380,13 +391,13 @@ func ReadPassword(opts GlobalOptions, prompt string) (string, error) { // ReadPasswordTwice calls ReadPassword two times and returns an error when the // passwords don't match. -func ReadPasswordTwice(gopts GlobalOptions, prompt1, prompt2 string) (string, error) { - pw1, err := ReadPassword(gopts, prompt1) +func ReadPasswordTwice(gopts GlobalOptions, prompt1, prompt2 string, allowInsecurePassword bool) (string, error) { + pw1, err := ReadPassword(gopts, prompt1, allowInsecurePassword) if err != nil { return "", err } if stdinIsTerminal() { - pw2, err := ReadPassword(gopts, prompt2) + pw2, err := ReadPassword(gopts, prompt2, allowInsecurePassword) if err != nil { return "", err } @@ -469,7 +480,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi } for ; passwordTriesLeft > 0; passwordTriesLeft-- { - opts.password, err = ReadPassword(opts, "enter password for repository: ") + opts.password, err = ReadPassword(opts, "enter password for repository: ", true) if err != nil && passwordTriesLeft > 1 { opts.password = "" fmt.Printf("%s. Try again\n", err) diff --git a/cmd/restic/secondary_repo.go b/cmd/restic/secondary_repo.go index 4c46b60df..3baab04fd 100644 --- a/cmd/restic/secondary_repo.go +++ b/cmd/restic/secondary_repo.go @@ -109,7 +109,8 @@ func fillSecondaryGlobalOpts(opts secondaryRepoOptions, gopts GlobalOptions, rep return GlobalOptions{}, false, err } } - dstGopts.password, err = ReadPassword(dstGopts, "enter password for "+repoPrefix+" repository: ") + //FIXME: constant true okay? + dstGopts.password, err = ReadPassword(dstGopts, "enter password for "+repoPrefix+" repository: ", true) if err != nil { return GlobalOptions{}, false, err } diff --git a/doc/bash-completion.sh b/doc/bash-completion.sh index cae37a6ca..b42b0294b 100644 --- a/doc/bash-completion.sh +++ b/doc/bash-completion.sh @@ -1442,6 +1442,7 @@ _restic_init() two_word_flags+=("--from-password-file") local_nonpersistent_flags+=("--from-password-file") local_nonpersistent_flags+=("--from-password-file=") + flags+=("--insecure-password") flags+=("--from-repo=") two_word_flags+=("--from-repo") local_nonpersistent_flags+=("--from-repo") @@ -1512,6 +1513,8 @@ _restic_key() command_aliases=() commands=() + commands+=("add") + commands+=("passwd") flags=() two_word_flags=() @@ -1582,6 +1585,42 @@ _restic_key() noun_aliases=() } +_restic_key_add() +{ + last_command="restic_key_add" + + command_aliases=() + + commands=() + + flags_with_completion=() + flags_completion=() + + flags+=("--insecure-password") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_restic_key_passwd() +{ + last_command="restic_key_passwd" + + command_aliases=() + + commands=() + + flags_with_completion=() + flags_completion=() + + flags+=("--insecure-password") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + _restic_list() { last_command="restic_list"