squash! Support to use an empty password if really desired

Support to use an empty password if really desired

Subcommands `init` and `key add|passwd` now support the flag
`--allow-empty-password` which disables the check for an empty password.
`restic` warns about it, though.
Support for `RESTIC_PASSWORD_FILE` and coresponding CLI option has been
added as well. You may user either an empty file or just `/dev/null`.

Basic support for Bash completion is there. But I am unsure if I got the
semantic right. Haven't done something like this before. It lacks
support for other shells.

Internal the empty password is represented by an arbitrary, non-valid
UTF-8 byte sequence (`0xff`) to allow to distinguish between not yet
loaded and empty password.
See https://stackoverflow.com/a/30741287.

This is just a PoC, I haven't ever written a single Go line before.
This commit is contained in:
doak 2024-02-12 22:42:32 +01:00
parent 66e7b0f34a
commit 92d1c11c22
5 changed files with 21 additions and 21 deletions

View File

@ -36,7 +36,7 @@ type InitOptions struct {
secondaryRepoOptions secondaryRepoOptions
CopyChunkerParameters bool CopyChunkerParameters bool
RepositoryVersion string RepositoryVersion string
InsecurePassword bool AllowEmptyPassword bool
} }
var initOptions InitOptions var initOptions InitOptions
@ -48,7 +48,7 @@ func init() {
initSecondaryRepoOptions(f, &initOptions.secondaryRepoOptions, "secondary", "to copy chunker parameters from") 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.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.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)") f.BoolVar(&initOptions.AllowEmptyPassword, "allow-empty-password", false, "allow an empty password (feel beeing warned)")
} }
func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []string) error { func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []string) error {
@ -85,7 +85,7 @@ func runInit(ctx context.Context, opts InitOptions, gopts GlobalOptions, args []
gopts.password, err = ReadPasswordTwice(gopts, gopts.password, err = ReadPasswordTwice(gopts,
"enter password for new repository: ", "enter password for new repository: ",
"enter password again: ", "enter password again: ",
opts.InsecurePassword) opts.AllowEmptyPassword)
if err != nil { if err != nil {
return err return err
} }

View File

@ -30,7 +30,7 @@ Exit status is 0 if the command is successful, and non-zero if there was any err
type KeyAddOptions struct { type KeyAddOptions struct {
NewPasswordFile string NewPasswordFile string
InsecurePassword bool AllowEmptyPassword bool
Username string Username string
Hostname string Hostname string
} }
@ -42,7 +42,7 @@ func init() {
flags := cmdKeyAdd.Flags() flags := cmdKeyAdd.Flags()
flags.StringVarP(&keyAddOpts.NewPasswordFile, "new-password-file", "", "", "`file` from which to read the new password") 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.BoolVar(&keyAddOpts.AllowEmptyPassword, "allow-empty-password", false, "allow an empty password (feel beeing warned)")
flags.StringVarP(&keyAddOpts.Username, "user", "", "", "the username for new key") flags.StringVarP(&keyAddOpts.Username, "user", "", "", "the username for new key")
flags.StringVarP(&keyAddOpts.Hostname, "host", "", "", "the hostname for new key") flags.StringVarP(&keyAddOpts.Hostname, "host", "", "", "the hostname for new key")
} }
@ -67,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 { func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyAddOptions) error {
pw, err := getNewPassword(gopts, opts.NewPasswordFile, opts.InsecurePassword) pw, err := getNewPassword(gopts, opts.NewPasswordFile, opts.AllowEmptyPassword)
if err != nil { if err != nil {
return err return err
} }
@ -90,7 +90,7 @@ func addKey(ctx context.Context, repo *repository.Repository, gopts GlobalOption
// testKeyNewPassword is used to set a new password during integration testing. // testKeyNewPassword is used to set a new password during integration testing.
var testKeyNewPassword string var testKeyNewPassword string
func getNewPassword(gopts GlobalOptions, newPasswordFile string, allowInsecurePassword bool) (string, error) { func getNewPassword(gopts GlobalOptions, newPasswordFile string, allowAllowEmptyPassword bool) (string, error) {
if testKeyNewPassword != "" { if testKeyNewPassword != "" {
return testKeyNewPassword, nil return testKeyNewPassword, nil
} }
@ -107,7 +107,7 @@ func getNewPassword(gopts GlobalOptions, newPasswordFile string, allowInsecurePa
return ReadPasswordTwice(newopts, return ReadPasswordTwice(newopts,
"enter new password: ", "enter new password: ",
"enter password again: ", "enter password again: ",
allowInsecurePassword) allowAllowEmptyPassword)
} }
func loadPasswordFromFile(pwdFile string) (string, error) { func loadPasswordFromFile(pwdFile string) (string, error) {

View File

@ -38,7 +38,7 @@ func init() {
flags := cmdKeyPasswd.Flags() flags := cmdKeyPasswd.Flags()
flags.StringVarP(&keyPasswdOpts.NewPasswordFile, "new-password-file", "", "", "`file` from which to read the new password") 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.BoolVar(&keyPasswdOpts.AllowEmptyPassword, "allow-empty-password", false, "allow an empty password (feel beeing warned)")
flags.StringVarP(&keyPasswdOpts.Username, "user", "", "", "the username for new key") flags.StringVarP(&keyPasswdOpts.Username, "user", "", "", "the username for new key")
flags.StringVarP(&keyPasswdOpts.Hostname, "host", "", "", "the hostname for new key") flags.StringVarP(&keyPasswdOpts.Hostname, "host", "", "", "the hostname for new key")
} }
@ -63,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 { func changePassword(ctx context.Context, repo *repository.Repository, gopts GlobalOptions, opts KeyPasswdOptions) error {
pw, err := getNewPassword(gopts, opts.NewPasswordFile, opts.InsecurePassword) pw, err := getNewPassword(gopts, opts.NewPasswordFile, opts.AllowEmptyPassword)
if err != nil { if err != nil {
return err return err
} }

View File

@ -356,7 +356,7 @@ const emptyPassword = "\xff"
// ReadPassword reads the password from a password file, the environment // ReadPassword reads the password from a password file, the environment
// variable RESTIC_PASSWORD or prompts the user. // variable RESTIC_PASSWORD or prompts the user.
func ReadPassword(opts GlobalOptions, prompt string, allowInsecurePassword bool) (string, error) { func ReadPassword(opts GlobalOptions, prompt string, allowAllowEmptyPassword bool) (string, error) {
if opts.password != "" { if opts.password != "" {
return opts.password, nil return opts.password, nil
} }
@ -378,7 +378,7 @@ func ReadPassword(opts GlobalOptions, prompt string, allowInsecurePassword bool)
} }
if len(password) == 0 { if len(password) == 0 {
if allowInsecurePassword { if allowAllowEmptyPassword {
Warnf("using an empty password is bad security practice\n") Warnf("using an empty password is bad security practice\n")
return emptyPassword, nil return emptyPassword, nil
} else { } else {
@ -391,13 +391,13 @@ func ReadPassword(opts GlobalOptions, prompt string, allowInsecurePassword bool)
// ReadPasswordTwice calls ReadPassword two times and returns an error when the // ReadPasswordTwice calls ReadPassword two times and returns an error when the
// passwords don't match. // passwords don't match.
func ReadPasswordTwice(gopts GlobalOptions, prompt1, prompt2 string, allowInsecurePassword bool) (string, error) { func ReadPasswordTwice(gopts GlobalOptions, prompt1, prompt2 string, allowAllowEmptyPassword bool) (string, error) {
pw1, err := ReadPassword(gopts, prompt1, allowInsecurePassword) pw1, err := ReadPassword(gopts, prompt1, allowAllowEmptyPassword)
if err != nil { if err != nil {
return "", err return "", err
} }
if stdinIsTerminal() { if stdinIsTerminal() {
pw2, err := ReadPassword(gopts, prompt2, allowInsecurePassword) pw2, err := ReadPassword(gopts, prompt2, allowAllowEmptyPassword)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -1442,7 +1442,7 @@ _restic_init()
two_word_flags+=("--from-password-file") two_word_flags+=("--from-password-file")
local_nonpersistent_flags+=("--from-password-file") local_nonpersistent_flags+=("--from-password-file")
local_nonpersistent_flags+=("--from-password-file=") local_nonpersistent_flags+=("--from-password-file=")
flags+=("--insecure-password") flags+=("--allow-empty-password")
flags+=("--from-repo=") flags+=("--from-repo=")
two_word_flags+=("--from-repo") two_word_flags+=("--from-repo")
local_nonpersistent_flags+=("--from-repo") local_nonpersistent_flags+=("--from-repo")
@ -1598,7 +1598,7 @@ _restic_key_add()
flags_with_completion=() flags_with_completion=()
flags_completion=() flags_completion=()
flags+=("--insecure-password") flags+=("--allow-empty-password")
must_have_one_flag=() must_have_one_flag=()
must_have_one_noun=() must_have_one_noun=()
@ -1632,7 +1632,7 @@ _restic_key_passwd()
flags_with_completion=() flags_with_completion=()
flags_completion=() flags_completion=()
flags+=("--insecure-password") flags+=("--allow-empty-password")
must_have_one_flag=() must_have_one_flag=()
must_have_one_noun=() must_have_one_noun=()