github.com/mckael/restic@v0.8.3/cmd/restic/cmd_key.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/restic/restic/internal/errors"
     8  	"github.com/restic/restic/internal/repository"
     9  	"github.com/restic/restic/internal/restic"
    10  
    11  	"github.com/spf13/cobra"
    12  )
    13  
    14  var cmdKey = &cobra.Command{
    15  	Use:   "key [list|add|remove|passwd] [ID]",
    16  	Short: "Manage keys (passwords)",
    17  	Long: `
    18  The "key" command manages keys (passwords) for accessing the repository.
    19  `,
    20  	DisableAutoGenTag: true,
    21  	RunE: func(cmd *cobra.Command, args []string) error {
    22  		return runKey(globalOptions, args)
    23  	},
    24  }
    25  
    26  func init() {
    27  	cmdRoot.AddCommand(cmdKey)
    28  }
    29  
    30  func listKeys(ctx context.Context, s *repository.Repository) error {
    31  	tab := NewTable()
    32  	tab.Header = fmt.Sprintf(" %-10s  %-10s  %-10s  %s", "ID", "User", "Host", "Created")
    33  	tab.RowFormat = "%s%-10s  %-10s  %-10s  %s"
    34  
    35  	err := s.List(ctx, restic.KeyFile, func(id restic.ID, size int64) error {
    36  		k, err := repository.LoadKey(ctx, s, id.String())
    37  		if err != nil {
    38  			Warnf("LoadKey() failed: %v\n", err)
    39  			return nil
    40  		}
    41  
    42  		var current string
    43  		if id.String() == s.KeyName() {
    44  			current = "*"
    45  		} else {
    46  			current = " "
    47  		}
    48  		tab.Rows = append(tab.Rows, []interface{}{current, id.Str(),
    49  			k.Username, k.Hostname, k.Created.Format(TimeFormat)})
    50  		return nil
    51  	})
    52  	if err != nil {
    53  		return err
    54  	}
    55  
    56  	return tab.Write(globalOptions.stdout)
    57  }
    58  
    59  // testKeyNewPassword is used to set a new password during integration testing.
    60  var testKeyNewPassword string
    61  
    62  func getNewPassword(gopts GlobalOptions) (string, error) {
    63  	if testKeyNewPassword != "" {
    64  		return testKeyNewPassword, nil
    65  	}
    66  
    67  	// Since we already have an open repository, temporary remove the password
    68  	// to prompt the user for the passwd.
    69  	newopts := gopts
    70  	newopts.password = ""
    71  
    72  	return ReadPasswordTwice(newopts,
    73  		"enter password for new key: ",
    74  		"enter password again: ")
    75  }
    76  
    77  func addKey(gopts GlobalOptions, repo *repository.Repository) error {
    78  	pw, err := getNewPassword(gopts)
    79  	if err != nil {
    80  		return err
    81  	}
    82  
    83  	id, err := repository.AddKey(gopts.ctx, repo, pw, repo.Key())
    84  	if err != nil {
    85  		return errors.Fatalf("creating new key failed: %v\n", err)
    86  	}
    87  
    88  	Verbosef("saved new key as %s\n", id)
    89  
    90  	return nil
    91  }
    92  
    93  func deleteKey(ctx context.Context, repo *repository.Repository, name string) error {
    94  	if name == repo.KeyName() {
    95  		return errors.Fatal("refusing to remove key currently used to access repository")
    96  	}
    97  
    98  	h := restic.Handle{Type: restic.KeyFile, Name: name}
    99  	err := repo.Backend().Remove(ctx, h)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	Verbosef("removed key %v\n", name)
   105  	return nil
   106  }
   107  
   108  func changePassword(gopts GlobalOptions, repo *repository.Repository) error {
   109  	pw, err := getNewPassword(gopts)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	id, err := repository.AddKey(gopts.ctx, repo, pw, repo.Key())
   115  	if err != nil {
   116  		return errors.Fatalf("creating new key failed: %v\n", err)
   117  	}
   118  
   119  	h := restic.Handle{Type: restic.KeyFile, Name: repo.KeyName()}
   120  	err = repo.Backend().Remove(gopts.ctx, h)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	Verbosef("saved new key as %s\n", id)
   126  
   127  	return nil
   128  }
   129  
   130  func runKey(gopts GlobalOptions, args []string) error {
   131  	if len(args) < 1 || (args[0] == "remove" && len(args) != 2) || (args[0] != "remove" && len(args) != 1) {
   132  		return errors.Fatal("wrong number of arguments")
   133  	}
   134  
   135  	ctx, cancel := context.WithCancel(gopts.ctx)
   136  	defer cancel()
   137  
   138  	repo, err := OpenRepository(gopts)
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	switch args[0] {
   144  	case "list":
   145  		lock, err := lockRepo(repo)
   146  		defer unlockRepo(lock)
   147  		if err != nil {
   148  			return err
   149  		}
   150  
   151  		return listKeys(ctx, repo)
   152  	case "add":
   153  		lock, err := lockRepo(repo)
   154  		defer unlockRepo(lock)
   155  		if err != nil {
   156  			return err
   157  		}
   158  
   159  		return addKey(gopts, repo)
   160  	case "remove":
   161  		lock, err := lockRepoExclusive(repo)
   162  		defer unlockRepo(lock)
   163  		if err != nil {
   164  			return err
   165  		}
   166  
   167  		id, err := restic.Find(repo.Backend(), restic.KeyFile, args[1])
   168  		if err != nil {
   169  			return err
   170  		}
   171  
   172  		return deleteKey(gopts.ctx, repo, id)
   173  	case "passwd":
   174  		lock, err := lockRepoExclusive(repo)
   175  		defer unlockRepo(lock)
   176  		if err != nil {
   177  			return err
   178  		}
   179  
   180  		return changePassword(gopts, repo)
   181  	}
   182  
   183  	return nil
   184  }