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