Skip to content

Commit 6e216ff

Browse files
Sort masterkeys according to decryption-order
Co-authored-by: Gabriel Martinez <19713226+GMartinez-Sisti@users.noreply.github.com> Signed-off-by: Boris Kreitchman <bkreitch@gmail.com>
1 parent 73ec51f commit 6e216ff

File tree

23 files changed

+342
-139
lines changed

23 files changed

+342
-139
lines changed

README.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ Given that, the only command a SOPS user needs is:
170170
encrypted if modified, and saved back to its original location. All of these
171171
steps, apart from the actual editing, are transparent to the user.
172172

173+
Order in which availible decryption methods are tried can by specified by ``--decryption-order`` option
174+
or **SOPS_DECRYPTION_ORDER** environment variable as comma separated list.
175+
Default order is ``age,pgp`` - meaning offline methods are tried first and then the remaining ones.
176+
173177
Test with the dev PGP key
174178
~~~~~~~~~~~~~~~~~~~~~~~~~
175179

age/keysource.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@ func (key *MasterKey) ToMap() map[string]interface{} {
225225
return out
226226
}
227227

228+
// TypeToString converts key type to a string
229+
func (key *MasterKey) TypeToString() string {
230+
return "age"
231+
}
232+
228233
func getUserConfigDir() (string, error) {
229234
if runtime.GOOS == "darwin" {
230235
if userConfigDir, ok := os.LookupEnv(xdgConfigHome); ok && userConfigDir != "" {

azkv/keysource.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
215215
return out
216216
}
217217

218+
// TypeToString converts key type to a string
219+
func (key *MasterKey) TypeToString() string {
220+
return "azure_kv"
221+
}
222+
218223
// getTokenCredential returns the tokenCredential of the MasterKey, or
219224
// azidentity.NewDefaultAzureCredential.
220225
func (key *MasterKey) getTokenCredential() (azcore.TokenCredential, error) {

cmd/sops/codes/codes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const (
2525
NoFileSpecified int = 100
2626
CouldNotRetrieveKey int = 128
2727
NoEncryptionKeyFound int = 111
28+
InvalidDecryptionKeyType int = 112
29+
DuplicateDecryptionKeyType int = 113
2830
FileHasNotBeenModified int = 200
2931
NoEditorFound int = 201
3032
FailedToCompareVersions int = 202

cmd/sops/common/common.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ type DecryptTreeOpts struct {
7070
// Tree is the tree to be decrypted
7171
Tree *sops.Tree
7272
// KeyServices are the key services to be used for decryption of the data key
73-
KeyServices []keyservice.KeyServiceClient
73+
KeyServices []keyservice.KeyServiceClient
74+
DecryptionOrder []string
7475
// IgnoreMac is whether or not to ignore the Message Authentication Code included in the SOPS tree
7576
IgnoreMac bool
7677
// Cipher is the cryptographic cipher to use to decrypt the values inside the tree
@@ -79,7 +80,7 @@ type DecryptTreeOpts struct {
7980

8081
// DecryptTree decrypts the tree passed in through the DecryptTreeOpts and additionally returns the decrypted data key
8182
func DecryptTree(opts DecryptTreeOpts) (dataKey []byte, err error) {
82-
dataKey, err = opts.Tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices)
83+
dataKey, err = opts.Tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices, opts.DecryptionOrder)
8384
if err != nil {
8485
return nil, NewExitError(err, codes.CouldNotRetrieveKey)
8586
}
@@ -221,11 +222,12 @@ func GetKMSKeyWithEncryptionCtx(tree *sops.Tree) (keyGroupIndex int, keyIndex in
221222

222223
// GenericDecryptOpts represents decryption options and config
223224
type GenericDecryptOpts struct {
224-
Cipher sops.Cipher
225-
InputStore sops.Store
226-
InputPath string
227-
IgnoreMAC bool
228-
KeyServices []keyservice.KeyServiceClient
225+
Cipher sops.Cipher
226+
InputStore sops.Store
227+
InputPath string
228+
IgnoreMAC bool
229+
KeyServices []keyservice.KeyServiceClient
230+
DecryptionOrder []string
229231
}
230232

231233
// LoadEncryptedFileWithBugFixes is a wrapper around LoadEncryptedFile which includes

cmd/sops/decrypt.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ const notBinaryHint = ("This is likely not an encrypted binary file?" +
1515
" If not, use --output-type to select the correct output type.")
1616

1717
type decryptOpts struct {
18-
Cipher sops.Cipher
19-
InputStore sops.Store
20-
OutputStore sops.Store
21-
InputPath string
22-
IgnoreMAC bool
23-
Extract []interface{}
24-
KeyServices []keyservice.KeyServiceClient
18+
Cipher sops.Cipher
19+
InputStore sops.Store
20+
OutputStore sops.Store
21+
InputPath string
22+
IgnoreMAC bool
23+
Extract []interface{}
24+
KeyServices []keyservice.KeyServiceClient
25+
DecryptionOrder []string
2526
}
2627

2728
func decrypt(opts decryptOpts) (decryptedFile []byte, err error) {
@@ -37,10 +38,11 @@ func decrypt(opts decryptOpts) (decryptedFile []byte, err error) {
3738
}
3839

3940
_, err = common.DecryptTree(common.DecryptTreeOpts{
40-
Cipher: opts.Cipher,
41-
IgnoreMac: opts.IgnoreMAC,
42-
Tree: tree,
43-
KeyServices: opts.KeyServices,
41+
Cipher: opts.Cipher,
42+
IgnoreMac: opts.IgnoreMAC,
43+
Tree: tree,
44+
KeyServices: opts.KeyServices,
45+
DecryptionOrder: opts.DecryptionOrder,
4446
})
4547
if err != nil {
4648
return nil, err

cmd/sops/edit.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ import (
2020
)
2121

2222
type editOpts struct {
23-
Cipher sops.Cipher
24-
InputStore common.Store
25-
OutputStore common.Store
26-
InputPath string
27-
IgnoreMAC bool
28-
KeyServices []keyservice.KeyServiceClient
29-
ShowMasterKeys bool
23+
Cipher sops.Cipher
24+
InputStore common.Store
25+
OutputStore common.Store
26+
InputPath string
27+
IgnoreMAC bool
28+
KeyServices []keyservice.KeyServiceClient
29+
DecryptionOrder []string
30+
ShowMasterKeys bool
3031
}
3132

3233
type editExampleOpts struct {
@@ -94,7 +95,11 @@ func edit(opts editOpts) ([]byte, error) {
9495
}
9596
// Decrypt the file
9697
dataKey, err := common.DecryptTree(common.DecryptTreeOpts{
97-
Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices,
98+
Cipher: opts.Cipher,
99+
IgnoreMac: opts.IgnoreMAC,
100+
Tree: tree,
101+
KeyServices: opts.KeyServices,
102+
DecryptionOrder: opts.DecryptionOrder,
98103
})
99104
if err != nil {
100105
return nil, err

cmd/sops/main.go

Lines changed: 91 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/getsops/sops/v3/version"
3939
"github.com/sirupsen/logrus"
4040
"github.com/urfave/cli"
41+
"golang.org/x/exp/slices"
4142
"google.golang.org/grpc"
4243
"google.golang.org/grpc/credentials/insecure"
4344
)
@@ -145,13 +146,19 @@ func main() {
145146
inputStore := inputStore(c, fileName)
146147

147148
svcs := keyservices(c)
149+
150+
order, err := decryptionOrder(c.String("decryption-order"))
151+
if err != nil {
152+
return toExitError(err)
153+
}
148154
opts := decryptOpts{
149-
OutputStore: &dotenv.Store{},
150-
InputStore: inputStore,
151-
InputPath: fileName,
152-
Cipher: aes.NewCipher(),
153-
KeyServices: svcs,
154-
IgnoreMAC: c.Bool("ignore-mac"),
155+
OutputStore: &dotenv.Store{},
156+
InputStore: inputStore,
157+
InputPath: fileName,
158+
Cipher: aes.NewCipher(),
159+
KeyServices: svcs,
160+
DecryptionOrder: order,
161+
IgnoreMAC: c.Bool("ignore-mac"),
155162
}
156163

157164
output, err := decrypt(opts)
@@ -213,13 +220,19 @@ func main() {
213220
outputStore := outputStore(c, fileName)
214221

215222
svcs := keyservices(c)
223+
224+
order, err := decryptionOrder(c.String("decryption-order"))
225+
if err != nil {
226+
return toExitError(err)
227+
}
216228
opts := decryptOpts{
217-
OutputStore: outputStore,
218-
InputStore: inputStore,
219-
InputPath: fileName,
220-
Cipher: aes.NewCipher(),
221-
KeyServices: svcs,
222-
IgnoreMAC: c.Bool("ignore-mac"),
229+
OutputStore: outputStore,
230+
InputStore: inputStore,
231+
InputPath: fileName,
232+
Cipher: aes.NewCipher(),
233+
KeyServices: svcs,
234+
DecryptionOrder: order,
235+
IgnoreMAC: c.Bool("ignore-mac"),
223236
}
224237

225238
output, err := decrypt(opts)
@@ -287,21 +300,25 @@ func main() {
287300
if info.IsDir() && !c.Bool("recursive") {
288301
return fmt.Errorf("can't operate on a directory without --recursive flag.")
289302
}
303+
order, err := decryptionOrder(c.String("decryption-order"))
304+
if err != nil {
305+
return toExitError(err)
306+
}
290307
err = filepath.Walk(path, func(subPath string, info os.FileInfo, err error) error {
291308
if err != nil {
292309
return toExitError(err)
293310
}
294311
if !info.IsDir() {
295312
err = publishcmd.Run(publishcmd.Opts{
296-
ConfigPath: configPath,
297-
InputPath: subPath,
298-
Cipher: aes.NewCipher(),
299-
KeyServices: keyservices(c),
300-
InputStore: inputStore(c, subPath),
301-
Interactive: !c.Bool("yes"),
302-
OmitExtensions: c.Bool("omit-extensions"),
303-
Recursive: c.Bool("recursive"),
304-
RootPath: path,
313+
ConfigPath: configPath,
314+
InputPath: subPath,
315+
Cipher: aes.NewCipher(),
316+
KeyServices: keyservices(c),
317+
DecryptionOrder: order,
318+
InputStore: inputStore(c, subPath),
319+
Interactive: !c.Bool("yes"),
320+
OmitExtensions: c.Bool("omit-extensions"),
321+
Recursive: c.Bool("recursive"),
305322
})
306323
if cliErr, ok := err.(*cli.ExitError); ok && cliErr != nil {
307324
return cliErr
@@ -708,6 +725,11 @@ func main() {
708725
Name: "output",
709726
Usage: "Save the output after encryption or decryption to the file specified",
710727
},
728+
cli.StringFlag{
729+
Name: "decryption-order",
730+
Usage: "comma separated list of decryption key types",
731+
EnvVar: "SOPS_DECRYPTION_ORDER",
732+
},
711733
}, keyserviceFlags...)
712734

713735
app.Action = func(c *cli.Context) error {
@@ -785,6 +807,10 @@ func main() {
785807
outputStore := outputStore(c, fileName)
786808
svcs := keyservices(c)
787809

810+
order, err := decryptionOrder(c.String("decryption-order"))
811+
if err != nil {
812+
return toExitError(err)
813+
}
788814
var output []byte
789815
if c.Bool("encrypt") {
790816
var groups []sops.KeyGroup
@@ -819,13 +845,14 @@ func main() {
819845
return common.NewExitError(fmt.Errorf("error parsing --extract path: %s", err), codes.InvalidTreePathFormat)
820846
}
821847
output, err = decrypt(decryptOpts{
822-
OutputStore: outputStore,
823-
InputStore: inputStore,
824-
InputPath: fileName,
825-
Cipher: aes.NewCipher(),
826-
Extract: extract,
827-
KeyServices: svcs,
828-
IgnoreMAC: c.Bool("ignore-mac"),
848+
OutputStore: outputStore,
849+
InputStore: inputStore,
850+
InputPath: fileName,
851+
Cipher: aes.NewCipher(),
852+
Extract: extract,
853+
KeyServices: svcs,
854+
DecryptionOrder: order,
855+
IgnoreMAC: c.Bool("ignore-mac"),
829856
})
830857
}
831858
if c.Bool("rotate") {
@@ -900,6 +927,7 @@ func main() {
900927
InputPath: fileName,
901928
Cipher: aes.NewCipher(),
902929
KeyServices: svcs,
930+
DecryptionOrder: order,
903931
IgnoreMAC: c.Bool("ignore-mac"),
904932
AddMasterKeys: addMasterKeys,
905933
RemoveMasterKeys: rmMasterKeys,
@@ -919,14 +947,15 @@ func main() {
919947
return toExitError(err)
920948
}
921949
output, err = set(setOpts{
922-
OutputStore: outputStore,
923-
InputStore: inputStore,
924-
InputPath: fileName,
925-
Cipher: aes.NewCipher(),
926-
KeyServices: svcs,
927-
IgnoreMAC: c.Bool("ignore-mac"),
928-
Value: value,
929-
TreePath: path,
950+
OutputStore: outputStore,
951+
InputStore: inputStore,
952+
InputPath: fileName,
953+
Cipher: aes.NewCipher(),
954+
KeyServices: svcs,
955+
DecryptionOrder: order,
956+
IgnoreMAC: c.Bool("ignore-mac"),
957+
Value: value,
958+
TreePath: path,
930959
})
931960
}
932961

@@ -935,13 +964,14 @@ func main() {
935964
_, statErr := os.Stat(fileName)
936965
fileExists := statErr == nil
937966
opts := editOpts{
938-
OutputStore: outputStore,
939-
InputStore: inputStore,
940-
InputPath: fileName,
941-
Cipher: aes.NewCipher(),
942-
KeyServices: svcs,
943-
IgnoreMAC: c.Bool("ignore-mac"),
944-
ShowMasterKeys: c.Bool("show-master-keys"),
967+
OutputStore: outputStore,
968+
InputStore: inputStore,
969+
InputPath: fileName,
970+
Cipher: aes.NewCipher(),
971+
KeyServices: svcs,
972+
DecryptionOrder: order,
973+
IgnoreMAC: c.Bool("ignore-mac"),
974+
ShowMasterKeys: c.Bool("show-master-keys"),
945975
}
946976
if fileExists {
947977
output, err = edit(opts)
@@ -1251,3 +1281,21 @@ func extractSetArguments(set string) (path []interface{}, valueToInsert interfac
12511281
}
12521282
return path, valueToInsert, nil
12531283
}
1284+
1285+
func decryptionOrder(decryptionOrder string) ([]string, error) {
1286+
if decryptionOrder == "" {
1287+
return sops.SopsDecryptionOrderDefault, nil
1288+
}
1289+
orderList := strings.Split(decryptionOrder, ",")
1290+
unique := make(map[string]bool)
1291+
for _, v := range orderList {
1292+
if !slices.Contains(sops.MasterKeyTypes, v) {
1293+
return nil, common.NewExitError(fmt.Sprintf("Invalid decryption key type: %s", v), codes.InvalidDecryptionKeyType)
1294+
}
1295+
if _, ok := unique[v]; ok {
1296+
return nil, common.NewExitError(fmt.Sprintf("Duplicate decryption key type: %s", v), codes.DuplicateDecryptionKeyType)
1297+
}
1298+
unique[v] = true
1299+
}
1300+
return orderList, nil
1301+
}

0 commit comments

Comments
 (0)