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 <ahktenzero@mohorovi.cc>
This commit is contained in:
Peter Schultz 2019-06-26 09:37:08 +02:00
parent 2b5a6d255a
commit 90fc639a67
4 changed files with 60 additions and 16 deletions

View file

@ -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

View file

@ -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)
}

View file

@ -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 {

View file

@ -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())