Merge pull request #613 from restic/read-password-from-file

Read password from file
This commit is contained in:
Alexander Neumann 2016-09-12 20:37:08 +02:00
commit ceb4a3ecc0
5 changed files with 61 additions and 26 deletions

View file

@ -116,7 +116,8 @@ Remembering your password is important! If you lose it, you won't be able to
access data stored in the repository. access data stored in the repository.
For automated backups, restic accepts the repository location in the For automated backups, restic accepts the repository location in the
environment variable `RESTIC_REPOSITORY` and also the password in the variable environment variable `RESTIC_REPOSITORY`. The password can be read from a file
(via the option `--password-file`) or the environment variable
`RESTIC_PASSWORD`. `RESTIC_PASSWORD`.
## Password prompt on Windows ## Password prompt on Windows

View file

@ -20,9 +20,12 @@ func (cmd CmdInit) Execute(args []string) error {
} }
if cmd.global.password == "" { if cmd.global.password == "" {
cmd.global.password = cmd.global.ReadPasswordTwice( cmd.global.password, err = cmd.global.ReadPasswordTwice(
"enter password for new backend: ", "enter password for new backend: ",
"enter password again: ") "enter password again: ")
if err != nil {
return err
}
} }
s := repository.New(be) s := repository.New(be)

View file

@ -56,9 +56,9 @@ func (cmd CmdKey) listKeys(s *repository.Repository) error {
return tab.Write(cmd.global.stdout) return tab.Write(cmd.global.stdout)
} }
func (cmd CmdKey) getNewPassword() string { func (cmd CmdKey) getNewPassword() (string, error) {
if cmd.newPassword != "" { if cmd.newPassword != "" {
return cmd.newPassword return cmd.newPassword, nil
} }
return cmd.global.ReadPasswordTwice( return cmd.global.ReadPasswordTwice(
@ -67,7 +67,12 @@ func (cmd CmdKey) getNewPassword() string {
} }
func (cmd CmdKey) addKey(repo *repository.Repository) error { func (cmd CmdKey) addKey(repo *repository.Repository) error {
id, err := repository.AddKey(repo, cmd.getNewPassword(), repo.Key()) pw, err := cmd.getNewPassword()
if err != nil {
return err
}
id, err := repository.AddKey(repo, pw, repo.Key())
if err != nil { if err != nil {
return errors.Fatalf("creating new key failed: %v\n", err) return errors.Fatalf("creating new key failed: %v\n", err)
} }
@ -92,7 +97,12 @@ func (cmd CmdKey) deleteKey(repo *repository.Repository, name string) error {
} }
func (cmd CmdKey) changePassword(repo *repository.Repository) error { func (cmd CmdKey) changePassword(repo *repository.Repository) error {
id, err := repository.AddKey(repo, cmd.getNewPassword(), repo.Key()) pw, err := cmd.getNewPassword()
if err != nil {
return err
}
id, err := repository.AddKey(repo, pw, repo.Key())
if err != nil { if err != nil {
return errors.Fatalf("creating new key failed: %v\n", err) return errors.Fatalf("creating new key failed: %v\n", err)
} }

View file

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"restic" "restic"
"runtime" "runtime"
@ -29,6 +30,7 @@ var compiledAt = "unknown time"
// GlobalOptions holds all those options that can be set for every command. // GlobalOptions holds all those options that can be set for every command.
type GlobalOptions struct { type GlobalOptions struct {
Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"` Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"`
PasswordFile string `short:"p" long:"password-file" description:"Read the repository password from a file"`
CacheDir string ` long:"cache-dir" description:"Directory to use as a local cache"` CacheDir string ` long:"cache-dir" description:"Directory to use as a local cache"`
Quiet bool `short:"q" long:"quiet" default:"false" description:"Do not output comprehensive progress report"` Quiet bool `short:"q" long:"quiet" default:"false" description:"Do not output comprehensive progress report"`
NoLock bool ` long:"no-lock" default:"false" description:"Do not lock the repo, this allows some operations on read-only repos."` NoLock bool ` long:"no-lock" default:"false" description:"Do not lock the repo, this allows some operations on read-only repos."`
@ -185,7 +187,7 @@ func readPassword(in io.Reader) (password string, err error) {
buf = buf[:n] buf = buf[:n]
if err != nil && errors.Cause(err) != io.ErrUnexpectedEOF { if err != nil && errors.Cause(err) != io.ErrUnexpectedEOF {
return "", err return "", errors.Wrap(err, "ReadFull")
} }
return strings.TrimRight(string(buf), "\r\n"), nil return strings.TrimRight(string(buf), "\r\n"), nil
@ -199,15 +201,25 @@ func readPasswordTerminal(in *os.File, out io.Writer, prompt string) (password s
buf, err := terminal.ReadPassword(int(in.Fd())) buf, err := terminal.ReadPassword(int(in.Fd()))
fmt.Fprintln(out) fmt.Fprintln(out)
if err != nil { if err != nil {
return "", err return "", errors.Wrap(err, "ReadPassword")
} }
password = string(buf) password = string(buf)
return password, nil return password, nil
} }
// ReadPassword reads the password from stdin. // ReadPassword reads the password from a password file, the environment
func (o GlobalOptions) ReadPassword(prompt string) string { // variable RESTIC_PASSWORD or prompts the user.
func (o GlobalOptions) ReadPassword(prompt string) (string, error) {
if o.PasswordFile != "" {
s, err := ioutil.ReadFile(o.PasswordFile)
return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile")
}
if pwd := os.Getenv("RESTIC_PASSWORD"); pwd != "" {
return pwd, nil
}
var ( var (
password string password string
err error err error
@ -220,26 +232,33 @@ func (o GlobalOptions) ReadPassword(prompt string) string {
} }
if err != nil { if err != nil {
o.Exitf(2, "unable to read password: %v", err) return "", errors.Wrap(err, "unable to read password")
} }
if len(password) == 0 { if len(password) == 0 {
o.Exitf(1, "an empty password is not a password") return "", errors.Fatal("an empty password is not a password")
} }
return password return password, nil
} }
// 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 (o GlobalOptions) ReadPasswordTwice(prompt1, prompt2 string) string { func (o GlobalOptions) ReadPasswordTwice(prompt1, prompt2 string) (string, error) {
pw1 := o.ReadPassword(prompt1) pw1, err := o.ReadPassword(prompt1)
pw2 := o.ReadPassword(prompt2) if err != nil {
if pw1 != pw2 { return "", err
o.Exitf(1, "passwords do not match") }
pw2, err := o.ReadPassword(prompt2)
if err != nil {
return "", err
} }
return pw1 if pw1 != pw2 {
return "", errors.Fatal("passwords do not match")
}
return pw1, nil
} }
const maxKeys = 20 const maxKeys = 20
@ -258,7 +277,10 @@ func (o GlobalOptions) OpenRepository() (*repository.Repository, error) {
s := repository.New(be) s := repository.New(be)
if o.password == "" { if o.password == "" {
o.password = o.ReadPassword("enter password for repository: ") o.password, err = o.ReadPassword("enter password for repository: ")
if err != nil {
return nil, err
}
} }
err = s.SearchKey(o.password, maxKeys) err = s.SearchKey(o.password, maxKeys)

View file

@ -28,7 +28,6 @@ func main() {
// defer profile.Start(profile.MemProfileRate(100000), profile.ProfilePath(".")).Stop() // defer profile.Start(profile.MemProfileRate(100000), profile.ProfilePath(".")).Stop()
// defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop() // defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop()
globalOpts.Repo = os.Getenv("RESTIC_REPOSITORY") globalOpts.Repo = os.Getenv("RESTIC_REPOSITORY")
globalOpts.password = os.Getenv("RESTIC_PASSWORD")
debug.Log("restic", "main %#v", os.Args) debug.Log("restic", "main %#v", os.Args)