From 90fc639a67e50a9a5043595558069a8967bc393a Mon Sep 17 00:00:00 2001 From: Peter Schultz Date: Wed, 26 Jun 2019 09:37:08 +0200 Subject: [PATCH] Allow specifying user and host when adding keys The username and hostname for new keys can be specified with the new --user and --host flags, respectively. The flags are used only by the `key add` command and are otherwise ignored. This allows adding keys with for a desired user and host without having to run restic as that particular user on that particular host, making automated key management easier. Co-authored-by: James TD Smith --- changelog/unreleased/issue-2175 | 9 +++++++++ cmd/restic/cmd_key.go | 12 +++++++++--- cmd/restic/integration_test.go | 24 ++++++++++++++++++++++++ internal/repository/key.go | 31 ++++++++++++++++++------------- 4 files changed, 60 insertions(+), 16 deletions(-) create mode 100644 changelog/unreleased/issue-2175 diff --git a/changelog/unreleased/issue-2175 b/changelog/unreleased/issue-2175 new file mode 100644 index 000000000..1b33ace63 --- /dev/null +++ b/changelog/unreleased/issue-2175 @@ -0,0 +1,9 @@ +Enhancement: Allow specifying user and host when creating keys + +When adding a new key to the repository, the username and hostname for the new +key can be specified on the command line. This allows overriding the defaults, +for example if you would prefer to use the FQDN to identify the host or if you +want to add keys for several different hosts without having to run the key add +command on those hosts. + +https://github.com/restic/restic/issues/2175 diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go index 4c056e1ef..034d113ce 100644 --- a/cmd/restic/cmd_key.go +++ b/cmd/restic/cmd_key.go @@ -32,13 +32,19 @@ Exit status is 0 if the command was successful, and non-zero if there was any er }, } -var newPasswordFile string +var ( + newPasswordFile string + keyUsername string + keyHostname string +) func init() { cmdRoot.AddCommand(cmdKey) flags := cmdKey.Flags() flags.StringVarP(&newPasswordFile, "new-password-file", "", "", "the file from which to load a new password") + flags.StringVarP(&keyUsername, "user", "", "", "the username for new keys") + flags.StringVarP(&keyHostname, "host", "", "", "the hostname for new keys") } func listKeys(ctx context.Context, s *repository.Repository, gopts GlobalOptions) error { @@ -120,7 +126,7 @@ func addKey(gopts GlobalOptions, repo *repository.Repository) error { return err } - id, err := repository.AddKey(gopts.ctx, repo, pw, repo.Key()) + id, err := repository.AddKey(gopts.ctx, repo, pw, keyUsername, keyHostname, repo.Key()) if err != nil { return errors.Fatalf("creating new key failed: %v\n", err) } @@ -151,7 +157,7 @@ func changePassword(gopts GlobalOptions, repo *repository.Repository) error { return err } - id, err := repository.AddKey(gopts.ctx, repo, pw, repo.Key()) + id, err := repository.AddKey(gopts.ctx, repo, pw, "", "", repo.Key()) if err != nil { return errors.Fatalf("creating new key failed: %v\n", err) } diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index 5a22fe5a2..cf75b1475 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -639,6 +639,28 @@ func testRunKeyAddNewKey(t testing.TB, newPassword string, gopts GlobalOptions) rtest.OK(t, runKey(gopts, []string{"add"})) } +func testRunKeyAddNewKeyUserHost(t testing.TB, gopts GlobalOptions) { + testKeyNewPassword = "john's geheimnis" + defer func() { + testKeyNewPassword = "" + keyUsername = "" + keyHostname = "" + }() + + cmdKey.Flags().Parse([]string{"--user=john", "--host=example.com"}) + + t.Log("adding key for john@example.com") + rtest.OK(t, runKey(gopts, []string{"add"})) + + repo, err := OpenRepository(gopts) + rtest.OK(t, err) + key, err := repository.SearchKey(gopts.ctx, repo, testKeyNewPassword, 1, "") + rtest.OK(t, err) + + rtest.Equals(t, "john", key.Username) + rtest.Equals(t, "example.com", key.Hostname) +} + func testRunKeyPasswd(t testing.TB, newPassword string, gopts GlobalOptions) { testKeyNewPassword = newPassword defer func() { @@ -681,6 +703,8 @@ func TestKeyAddRemove(t *testing.T) { t.Logf("testing access with last password %q\n", env.gopts.password) rtest.OK(t, runKey(env.gopts, []string{"list"})) testRunCheck(t, env.gopts) + + testRunKeyAddNewKeyUserHost(t, env.gopts) } func testFileSize(filename string, size int64) error { diff --git a/internal/repository/key.go b/internal/repository/key.go index 6705ceb28..69eb7e04b 100644 --- a/internal/repository/key.go +++ b/internal/repository/key.go @@ -58,7 +58,7 @@ var ( // createMasterKey creates a new master key in the given backend and encrypts // it with the password. func createMasterKey(s *Repository, password string) (*Key, error) { - return AddKey(context.TODO(), s, password, nil) + return AddKey(context.TODO(), s, password, "", "", nil) } // OpenKey tries do decrypt the key specified by name with the given password. @@ -199,7 +199,7 @@ func LoadKey(ctx context.Context, s *Repository, name string) (k *Key, err error } // AddKey adds a new key to an already existing repository. -func AddKey(ctx context.Context, s *Repository, password string, template *crypto.Key) (*Key, error) { +func AddKey(ctx context.Context, s *Repository, password, username, hostname string, template *crypto.Key) (*Key, error) { // make sure we have valid KDF parameters if Params == nil { p, err := crypto.Calibrate(KDFTimeout, KDFMemory) @@ -213,24 +213,29 @@ func AddKey(ctx context.Context, s *Repository, password string, template *crypt // fill meta data about key newkey := &Key{ - Created: time.Now(), - KDF: "scrypt", - N: Params.N, - R: Params.R, - P: Params.P, + Created: time.Now(), + Username: username, + Hostname: hostname, + + KDF: "scrypt", + N: Params.N, + R: Params.R, + P: Params.P, } - hn, err := os.Hostname() - if err == nil { - newkey.Hostname = hn + if newkey.Hostname == "" { + newkey.Hostname, _ = os.Hostname() } - usr, err := user.Current() - if err == nil { - newkey.Username = usr.Username + if newkey.Username == "" { + usr, err := user.Current() + if err == nil { + newkey.Username = usr.Username + } } // generate random salt + var err error newkey.Salt, err = crypto.NewSalt() if err != nil { panic("unable to read enough random bytes for salt: " + err.Error())