Add support for reading password from external command

This allows reading the password from an password manager (like "pass").

Signed-off-by: Juergen Hoetzel <juergen@archlinux.org>
This commit is contained in:
Juergen Hoetzel 2018-11-18 14:31:00 +01:00
parent 2434ab2106
commit df7f72cdde
2 changed files with 34 additions and 15 deletions

View File

@ -34,6 +34,7 @@ import (
"github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/errors"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
"os/exec"
) )
var version = "0.9.3-dev (compiled manually)" var version = "0.9.3-dev (compiled manually)"
@ -43,18 +44,19 @@ const TimeFormat = "2006-01-02 15:04:05"
// GlobalOptions hold all global options for restic. // GlobalOptions hold all global options for restic.
type GlobalOptions struct { type GlobalOptions struct {
Repo string Repo string
PasswordFile string PasswordFile string
KeyHint string PasswordCommand string
Quiet bool KeyHint string
Verbose int Quiet bool
NoLock bool Verbose int
JSON bool NoLock bool
CacheDir string JSON bool
NoCache bool CacheDir string
CACerts []string NoCache bool
TLSClientCert string CACerts []string
CleanupCache bool TLSClientCert string
CleanupCache bool
LimitUploadKb int LimitUploadKb int
LimitDownloadKb int LimitDownloadKb int
@ -93,6 +95,7 @@ func init() {
f.StringVarP(&globalOptions.Repo, "repo", "r", os.Getenv("RESTIC_REPOSITORY"), "repository to backup to or restore from (default: $RESTIC_REPOSITORY)") f.StringVarP(&globalOptions.Repo, "repo", "r", os.Getenv("RESTIC_REPOSITORY"), "repository to backup to or restore from (default: $RESTIC_REPOSITORY)")
f.StringVarP(&globalOptions.PasswordFile, "password-file", "p", os.Getenv("RESTIC_PASSWORD_FILE"), "read the repository password from a file (default: $RESTIC_PASSWORD_FILE)") f.StringVarP(&globalOptions.PasswordFile, "password-file", "p", os.Getenv("RESTIC_PASSWORD_FILE"), "read the repository password from a file (default: $RESTIC_PASSWORD_FILE)")
f.StringVarP(&globalOptions.KeyHint, "key-hint", "", os.Getenv("RESTIC_KEY_HINT"), "key ID of key to try decrypting first (default: $RESTIC_KEY_HINT)") f.StringVarP(&globalOptions.KeyHint, "key-hint", "", os.Getenv("RESTIC_KEY_HINT"), "key ID of key to try decrypting first (default: $RESTIC_KEY_HINT)")
f.StringVarP(&globalOptions.PasswordCommand, "password-command", "", os.Getenv("RESTIC_PASSWORD_COMMAND"), "specify a shell command to obtain a password (default: $RESTIC_PASSWORD_COMMAND)")
f.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "do not output comprehensive progress report") f.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "do not output comprehensive progress report")
f.CountVarP(&globalOptions.Verbose, "verbose", "v", "be verbose (specify --verbose multiple times or level `n`)") f.CountVarP(&globalOptions.Verbose, "verbose", "v", "be verbose (specify --verbose multiple times or level `n`)")
f.BoolVar(&globalOptions.NoLock, "no-lock", false, "do not lock the repo, this allows some operations on read-only repos") f.BoolVar(&globalOptions.NoLock, "no-lock", false, "do not lock the repo, this allows some operations on read-only repos")
@ -238,7 +241,23 @@ func Exitf(exitcode int, format string, args ...interface{}) {
} }
// resolvePassword determines the password to be used for opening the repository. // resolvePassword determines the password to be used for opening the repository.
func resolvePassword(opts GlobalOptions, env string) (string, error) { func resolvePassword(opts GlobalOptions) (string, error) {
if opts.PasswordFile != "" && opts.PasswordCommand != "" {
return "", errors.Fatalf("Password file and command are mutually exclusive options")
}
if opts.PasswordCommand != "" {
args, err := backend.SplitShellStrings(opts.PasswordCommand)
if err != nil {
return "", err
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Stderr = os.Stderr
output, err := cmd.Output()
if err != nil {
return "", err
}
return (strings.TrimSpace(string(output))), nil
}
if opts.PasswordFile != "" { if opts.PasswordFile != "" {
s, err := textfile.Read(opts.PasswordFile) s, err := textfile.Read(opts.PasswordFile)
if os.IsNotExist(errors.Cause(err)) { if os.IsNotExist(errors.Cause(err)) {
@ -247,7 +266,7 @@ func resolvePassword(opts GlobalOptions, env string) (string, error) {
return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile") return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile")
} }
if pwd := os.Getenv(env); pwd != "" { if pwd := os.Getenv("RESTIC_PASSWORD"); pwd != "" {
return pwd, nil return pwd, nil
} }

View File

@ -54,7 +54,7 @@ directories in an encrypted repository stored on different backends.
if c.Name() == "version" { if c.Name() == "version" {
return nil return nil
} }
pwd, err := resolvePassword(globalOptions, "RESTIC_PASSWORD") pwd, err := resolvePassword(globalOptions)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Resolving password failed: %v\n", err) fmt.Fprintf(os.Stderr, "Resolving password failed: %v\n", err)
Exit(1) Exit(1)