Skip to content

Commit ec6de65

Browse files
bkreitchGMartinez-SistifelixfonteinPh0tonichiddeco
committed
Sort masterkeys according to decryption-order
Co-authored-by: Gabriel Martinez <19713226+GMartinez-Sisti@users.noreply.github.com> Co-authored-by: Felix Fontein <felix@fontein.de> Co-authored-by: Bastien Wermeille <bastien.wermeille@gmail.com> Co-authored-by: Hidde Beydals <hiddeco@users.noreply.github.com> Signed-off-by: Boris Kreitchman <bkreitch@gmail.com>
1 parent b54fa8a commit ec6de65

File tree

21 files changed

+355
-132
lines changed

21 files changed

+355
-132
lines changed

README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ 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+
The order in which available decryption methods are tried can be specified with
174+
``--decryption-order`` option or **SOPS_DECRYPTION_ORDER** environment variable
175+
as a comma separated list. The default order is ``age,pgp``. Offline methods are
176+
tried first and then the remaining ones.
177+
173178
Test with the dev PGP key
174179
~~~~~~~~~~~~~~~~~~~~~~~~~
175180

age/keysource.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const (
2828
SopsAgeKeyUserConfigPath = "sops/age/keys.txt"
2929
// On macOS, os.UserConfigDir() ignores XDG_CONFIG_HOME. So we handle that manually.
3030
xdgConfigHome = "XDG_CONFIG_HOME"
31+
// String representation of the key type
32+
KeyTypeString = "age"
3133
)
3234

3335
// log is the global logger for any age MasterKey.
@@ -225,6 +227,11 @@ func (key *MasterKey) ToMap() map[string]interface{} {
225227
return out
226228
}
227229

230+
// TypeToString converts key type to a string.
231+
func (key *MasterKey) TypeToString() string {
232+
return KeyTypeString
233+
}
234+
228235
func getUserConfigDir() (string, error) {
229236
if runtime.GOOS == "darwin" {
230237
if userConfigDir, ok := os.LookupEnv(xdgConfigHome); ok && userConfigDir != "" {

azkv/keysource.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ import (
2222
"github.com/getsops/sops/v3/logging"
2323
)
2424

25+
const (
26+
// String representation of the key type
27+
KeyTypeString = "azure_kv"
28+
)
29+
2530
var (
2631
// log is the global logger for any Azure Key Vault MasterKey.
2732
log *logrus.Logger
@@ -215,6 +220,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
215220
return out
216221
}
217222

223+
// TypeToString converts key type to a string.
224+
func (key *MasterKey) TypeToString() string {
225+
return KeyTypeString
226+
}
227+
218228
// getTokenCredential returns the tokenCredential of the MasterKey, or
219229
// azidentity.NewDefaultAzureCredential.
220230
func (key *MasterKey) getTokenCredential() (azcore.TokenCredential, error) {

cmd/sops/codes/codes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const (
2525
NoFileSpecified int = 100
2626
CouldNotRetrieveKey int = 128
2727
NoEncryptionKeyFound int = 111
28+
DuplicateDecryptionKeyType int = 112
2829
FileHasNotBeenModified int = 200
2930
NoEditorFound int = 201
3031
FailedToCompareVersions int = 202

cmd/sops/common/common.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ type DecryptTreeOpts struct {
7272
Tree *sops.Tree
7373
// KeyServices are the key services to be used for decryption of the data key
7474
KeyServices []keyservice.KeyServiceClient
75+
// DecryptionOrder is the order in which available decryption methods are tried
76+
DecryptionOrder []string
7577
// IgnoreMac is whether or not to ignore the Message Authentication Code included in the SOPS tree
7678
IgnoreMac bool
7779
// Cipher is the cryptographic cipher to use to decrypt the values inside the tree
@@ -80,7 +82,7 @@ type DecryptTreeOpts struct {
8082

8183
// DecryptTree decrypts the tree passed in through the DecryptTreeOpts and additionally returns the decrypted data key
8284
func DecryptTree(opts DecryptTreeOpts) (dataKey []byte, err error) {
83-
dataKey, err = opts.Tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices)
85+
dataKey, err = opts.Tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices, opts.DecryptionOrder)
8486
if err != nil {
8587
return nil, NewExitError(err, codes.CouldNotRetrieveKey)
8688
}
@@ -222,11 +224,12 @@ func GetKMSKeyWithEncryptionCtx(tree *sops.Tree) (keyGroupIndex int, keyIndex in
222224

223225
// GenericDecryptOpts represents decryption options and config
224226
type GenericDecryptOpts struct {
225-
Cipher sops.Cipher
226-
InputStore sops.Store
227-
InputPath string
228-
IgnoreMAC bool
229-
KeyServices []keyservice.KeyServiceClient
227+
Cipher sops.Cipher
228+
InputStore sops.Store
229+
InputPath string
230+
IgnoreMAC bool
231+
KeyServices []keyservice.KeyServiceClient
232+
DecryptionOrder []string
230233
}
231234

232235
// 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 {
@@ -96,7 +97,11 @@ func edit(opts editOpts) ([]byte, error) {
9697
}
9798
// Decrypt the file
9899
dataKey, err := common.DecryptTree(common.DecryptTreeOpts{
99-
Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices,
100+
Cipher: opts.Cipher,
101+
IgnoreMac: opts.IgnoreMAC,
102+
Tree: tree,
103+
KeyServices: opts.KeyServices,
104+
DecryptionOrder: opts.DecryptionOrder,
100105
})
101106
if err != nil {
102107
return nil, err

cmd/sops/main.go

Lines changed: 87 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,19 @@ func main() {
157157
inputStore := inputStore(c, fileName)
158158

159159
svcs := keyservices(c)
160+
161+
order, err := decryptionOrder(c.String("decryption-order"))
162+
if err != nil {
163+
return toExitError(err)
164+
}
160165
opts := decryptOpts{
161-
OutputStore: &dotenv.Store{},
162-
InputStore: inputStore,
163-
InputPath: fileName,
164-
Cipher: aes.NewCipher(),
165-
KeyServices: svcs,
166-
IgnoreMAC: c.Bool("ignore-mac"),
166+
OutputStore: &dotenv.Store{},
167+
InputStore: inputStore,
168+
InputPath: fileName,
169+
Cipher: aes.NewCipher(),
170+
KeyServices: svcs,
171+
DecryptionOrder: order,
172+
IgnoreMAC: c.Bool("ignore-mac"),
167173
}
168174

169175
output, err := decrypt(opts)
@@ -226,13 +232,19 @@ func main() {
226232
outputStore := outputStore(c, fileName)
227233

228234
svcs := keyservices(c)
235+
236+
order, err := decryptionOrder(c.String("decryption-order"))
237+
if err != nil {
238+
return toExitError(err)
239+
}
229240
opts := decryptOpts{
230-
OutputStore: outputStore,
231-
InputStore: inputStore,
232-
InputPath: fileName,
233-
Cipher: aes.NewCipher(),
234-
KeyServices: svcs,
235-
IgnoreMAC: c.Bool("ignore-mac"),
241+
OutputStore: outputStore,
242+
InputStore: inputStore,
243+
InputPath: fileName,
244+
Cipher: aes.NewCipher(),
245+
KeyServices: svcs,
246+
DecryptionOrder: order,
247+
IgnoreMAC: c.Bool("ignore-mac"),
236248
}
237249

238250
output, err := decrypt(opts)
@@ -300,21 +312,25 @@ func main() {
300312
if info.IsDir() && !c.Bool("recursive") {
301313
return fmt.Errorf("can't operate on a directory without --recursive flag.")
302314
}
315+
order, err := decryptionOrder(c.String("decryption-order"))
316+
if err != nil {
317+
return toExitError(err)
318+
}
303319
err = filepath.Walk(path, func(subPath string, info os.FileInfo, err error) error {
304320
if err != nil {
305321
return toExitError(err)
306322
}
307323
if !info.IsDir() {
308324
err = publishcmd.Run(publishcmd.Opts{
309-
ConfigPath: configPath,
310-
InputPath: subPath,
311-
Cipher: aes.NewCipher(),
312-
KeyServices: keyservices(c),
313-
InputStore: inputStore(c, subPath),
314-
Interactive: !c.Bool("yes"),
315-
OmitExtensions: c.Bool("omit-extensions"),
316-
Recursive: c.Bool("recursive"),
317-
RootPath: path,
325+
ConfigPath: configPath,
326+
InputPath: subPath,
327+
Cipher: aes.NewCipher(),
328+
KeyServices: keyservices(c),
329+
DecryptionOrder: order,
330+
InputStore: inputStore(c, subPath),
331+
Interactive: !c.Bool("yes"),
332+
OmitExtensions: c.Bool("omit-extensions"),
333+
Recursive: c.Bool("recursive"),
318334
})
319335
if cliErr, ok := err.(*cli.ExitError); ok && cliErr != nil {
320336
return cliErr
@@ -746,6 +762,11 @@ func main() {
746762
Name: "output",
747763
Usage: "Save the output after encryption or decryption to the file specified",
748764
},
765+
cli.StringFlag{
766+
Name: "decryption-order",
767+
Usage: "comma separated list of decryption key types",
768+
EnvVar: "SOPS_DECRYPTION_ORDER",
769+
},
749770
}, keyserviceFlags...)
750771

751772
app.Action = func(c *cli.Context) error {
@@ -827,6 +848,10 @@ func main() {
827848
outputStore := outputStore(c, fileName)
828849
svcs := keyservices(c)
829850

851+
order, err := decryptionOrder(c.String("decryption-order"))
852+
if err != nil {
853+
return toExitError(err)
854+
}
830855
var output []byte
831856
if c.Bool("encrypt") {
832857
var groups []sops.KeyGroup
@@ -862,13 +887,14 @@ func main() {
862887
return common.NewExitError(fmt.Errorf("error parsing --extract path: %s", err), codes.InvalidTreePathFormat)
863888
}
864889
output, err = decrypt(decryptOpts{
865-
OutputStore: outputStore,
866-
InputStore: inputStore,
867-
InputPath: fileName,
868-
Cipher: aes.NewCipher(),
869-
Extract: extract,
870-
KeyServices: svcs,
871-
IgnoreMAC: c.Bool("ignore-mac"),
890+
OutputStore: outputStore,
891+
InputStore: inputStore,
892+
InputPath: fileName,
893+
Cipher: aes.NewCipher(),
894+
Extract: extract,
895+
KeyServices: svcs,
896+
DecryptionOrder: order,
897+
IgnoreMAC: c.Bool("ignore-mac"),
872898
})
873899
}
874900
if c.Bool("rotate") {
@@ -943,6 +969,7 @@ func main() {
943969
InputPath: fileName,
944970
Cipher: aes.NewCipher(),
945971
KeyServices: svcs,
972+
DecryptionOrder: order,
946973
IgnoreMAC: c.Bool("ignore-mac"),
947974
AddMasterKeys: addMasterKeys,
948975
RemoveMasterKeys: rmMasterKeys,
@@ -962,14 +989,15 @@ func main() {
962989
return toExitError(err)
963990
}
964991
output, err = set(setOpts{
965-
OutputStore: outputStore,
966-
InputStore: inputStore,
967-
InputPath: fileName,
968-
Cipher: aes.NewCipher(),
969-
KeyServices: svcs,
970-
IgnoreMAC: c.Bool("ignore-mac"),
971-
Value: value,
972-
TreePath: path,
992+
OutputStore: outputStore,
993+
InputStore: inputStore,
994+
InputPath: fileName,
995+
Cipher: aes.NewCipher(),
996+
KeyServices: svcs,
997+
DecryptionOrder: order,
998+
IgnoreMAC: c.Bool("ignore-mac"),
999+
Value: value,
1000+
TreePath: path,
9731001
})
9741002
}
9751003

@@ -978,13 +1006,14 @@ func main() {
9781006
_, statErr := os.Stat(fileName)
9791007
fileExists := statErr == nil
9801008
opts := editOpts{
981-
OutputStore: outputStore,
982-
InputStore: inputStore,
983-
InputPath: fileName,
984-
Cipher: aes.NewCipher(),
985-
KeyServices: svcs,
986-
IgnoreMAC: c.Bool("ignore-mac"),
987-
ShowMasterKeys: c.Bool("show-master-keys"),
1009+
OutputStore: outputStore,
1010+
InputStore: inputStore,
1011+
InputPath: fileName,
1012+
Cipher: aes.NewCipher(),
1013+
KeyServices: svcs,
1014+
DecryptionOrder: order,
1015+
IgnoreMAC: c.Bool("ignore-mac"),
1016+
ShowMasterKeys: c.Bool("show-master-keys"),
9881017
}
9891018
if fileExists {
9901019
output, err = edit(opts)
@@ -1319,3 +1348,18 @@ func extractSetArguments(set string) (path []interface{}, valueToInsert interfac
13191348
}
13201349
return path, valueToInsert, nil
13211350
}
1351+
1352+
func decryptionOrder(decryptionOrder string) ([]string, error) {
1353+
if decryptionOrder == "" {
1354+
return sops.SopsDecryptionOrderDefault, nil
1355+
}
1356+
orderList := strings.Split(decryptionOrder, ",")
1357+
unique := make(map[string]bool)
1358+
for _, v := range orderList {
1359+
if _, ok := unique[v]; ok {
1360+
return nil, common.NewExitError(fmt.Sprintf("Duplicate decryption key type: %s", v), codes.DuplicateDecryptionKeyType)
1361+
}
1362+
unique[v] = true
1363+
}
1364+
return orderList, nil
1365+
}

0 commit comments

Comments
 (0)