1package updatekeys 2 3import ( 4 "fmt" 5 "log" 6 "os" 7 "path/filepath" 8 9 "go.mozilla.org/sops/v3/cmd/sops/codes" 10 "go.mozilla.org/sops/v3/cmd/sops/common" 11 "go.mozilla.org/sops/v3/config" 12 "go.mozilla.org/sops/v3/keyservice" 13) 14 15// Opts represents key operation options and config 16type Opts struct { 17 InputPath string 18 GroupQuorum int 19 KeyServices []keyservice.KeyServiceClient 20 Interactive bool 21 ConfigPath string 22} 23 24// UpdateKeys update the keys for a given file 25func UpdateKeys(opts Opts) error { 26 path, err := filepath.Abs(opts.InputPath) 27 if err != nil { 28 return err 29 } 30 info, err := os.Stat(path) 31 if err != nil { 32 return err 33 } 34 if info.IsDir() { 35 return fmt.Errorf("can't operate on a directory") 36 } 37 opts.InputPath = path 38 return updateFile(opts) 39} 40 41func updateFile(opts Opts) error { 42 store := common.DefaultStoreForPath(opts.InputPath) 43 log.Printf("Syncing keys for file %s", opts.InputPath) 44 tree, err := common.LoadEncryptedFile(store, opts.InputPath) 45 if err != nil { 46 return err 47 } 48 conf, err := config.LoadCreationRuleForFile(opts.ConfigPath, opts.InputPath, make(map[string]*string)) 49 if err != nil { 50 return err 51 } 52 53 diffs := common.DiffKeyGroups(tree.Metadata.KeyGroups, conf.KeyGroups) 54 keysWillChange := false 55 for _, diff := range diffs { 56 if len(diff.Added) > 0 || len(diff.Removed) > 0 { 57 keysWillChange = true 58 } 59 } 60 if !keysWillChange { 61 log.Printf("File %s already up to date", opts.InputPath) 62 return nil 63 } 64 fmt.Printf("The following changes will be made to the file's groups:\n") 65 common.PrettyPrintDiffs(diffs) 66 67 if opts.Interactive { 68 var response string 69 for response != "y" && response != "n" { 70 fmt.Printf("Is this okay? (y/n):") 71 _, err = fmt.Scanln(&response) 72 if err != nil { 73 return err 74 } 75 } 76 if response == "n" { 77 log.Printf("File %s left unchanged", opts.InputPath) 78 return nil 79 } 80 } 81 key, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices) 82 if err != nil { 83 return common.NewExitError(err, codes.CouldNotRetrieveKey) 84 } 85 tree.Metadata.KeyGroups = conf.KeyGroups 86 if opts.GroupQuorum != 0 { 87 tree.Metadata.ShamirThreshold = opts.GroupQuorum 88 } 89 tree.Metadata.ShamirThreshold = min(tree.Metadata.ShamirThreshold, len(tree.Metadata.KeyGroups)) 90 errs := tree.Metadata.UpdateMasterKeysWithKeyServices(key, opts.KeyServices) 91 if len(errs) > 0 { 92 return fmt.Errorf("error updating one or more master keys: %s", errs) 93 } 94 output, err := store.EmitEncryptedFile(*tree) 95 if err != nil { 96 return common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree) 97 } 98 outputFile, err := os.Create(opts.InputPath) 99 if err != nil { 100 return fmt.Errorf("could not open file for writing: %s", err) 101 } 102 defer outputFile.Close() 103 _, err = outputFile.Write(output) 104 if err != nil { 105 return fmt.Errorf("error writing to file: %s", err) 106 } 107 log.Printf("File %s synced with new keys", opts.InputPath) 108 return nil 109} 110 111func min(a, b int) int { 112 if a < b { 113 return a 114 } 115 return b 116} 117