diff --git a/cmd/sops/encrypt.go b/cmd/sops/encrypt.go index 826fa496a..266f48ae3 100644 --- a/cmd/sops/encrypt.go +++ b/cmd/sops/encrypt.go @@ -36,7 +36,8 @@ func (err *fileAlreadyEncryptedError) Error() string { func (err *fileAlreadyEncryptedError) UserError() string { message := "The file you have provided contains a top-level entry called " + - "'sops'. This is generally due to the file already being encrypted. " + + "'sops', or for flat file formats top-level entries starting with " + + "'sops_'. This is generally due to the file already being encrypted. " + "SOPS uses a top-level entry called 'sops' to store the metadata " + "required to decrypt the file. For this reason, SOPS can not " + "encrypt files that already contain such an entry.\n\n" + @@ -47,10 +48,8 @@ func (err *fileAlreadyEncryptedError) UserError() string { } func ensureNoMetadata(opts encryptOpts, branch sops.TreeBranch) error { - for _, b := range branch { - if b.Key == "sops" { - return &fileAlreadyEncryptedError{} - } + if opts.OutputStore.HasSopsTopLevelKey(branch) { + return &fileAlreadyEncryptedError{} } return nil } diff --git a/sops.go b/sops.go index c144db005..672d32499 100644 --- a/sops.go +++ b/sops.go @@ -567,6 +567,12 @@ type ValueEmitter interface { EmitValue(interface{}) ([]byte, error) } +// CheckEncryped is the interface for testing whether a branch contains sops +// metadata. This is used to check whether a file is already encrypted or not. +type CheckEncryped interface { + HasSopsTopLevelKey(TreeBranch) bool +} + // Store is used to interact with files, both encrypted and unencrypted. type Store interface { EncryptedFileLoader @@ -574,6 +580,7 @@ type Store interface { EncryptedFileEmitter PlainFileEmitter ValueEmitter + CheckEncryped } // MasterKeyCount returns the number of master keys available diff --git a/stores/dotenv/store.go b/stores/dotenv/store.go index 9433322ee..88eb0442f 100644 --- a/stores/dotenv/store.go +++ b/stores/dotenv/store.go @@ -175,3 +175,15 @@ func isComplexValue(v interface{}) bool { } return false } + +// HasSopsTopLevelKey checks whether a top-level "sops" key exists. +func (store *Store) HasSopsTopLevelKey(branch sops.TreeBranch) bool { + for _, b := range branch { + if key, ok := b.Key.(string); ok { + if strings.HasPrefix(key, SopsPrefix) { + return true + } + } + } + return false +} diff --git a/stores/dotenv/store_test.go b/stores/dotenv/store_test.go index c234d4ea5..6886fbc34 100644 --- a/stores/dotenv/store_test.go +++ b/stores/dotenv/store_test.go @@ -80,3 +80,20 @@ func TestEmitEncryptedFileStability(t *testing.T) { previous = bytes } } + +func TestHasSopsTopLevelKey(t *testing.T) { + ok := (&Store{}).HasSopsTopLevelKey(sops.TreeBranch{ + sops.TreeItem{ + Key: "sops", + Value: "value", + }, + }) + assert.Equal(t, ok, false) + ok = (&Store{}).HasSopsTopLevelKey(sops.TreeBranch{ + sops.TreeItem{ + Key: "sops_", + Value: "value", + }, + }) + assert.Equal(t, ok, true) +} diff --git a/stores/ini/store.go b/stores/ini/store.go index dd1a77c63..c44b89302 100644 --- a/stores/ini/store.go +++ b/stores/ini/store.go @@ -274,3 +274,8 @@ func (store *Store) EmitExample() []byte { } return bytes } + +// HasSopsTopLevelKey checks whether a top-level "sops" key exists. +func (store *Store) HasSopsTopLevelKey(branch sops.TreeBranch) bool { + return stores.HasSopsTopLevelKey(branch) +} diff --git a/stores/json/store.go b/stores/json/store.go index cbecd8362..2f0f7a721 100644 --- a/stores/json/store.go +++ b/stores/json/store.go @@ -357,3 +357,13 @@ func (store *Store) EmitExample() []byte { } return bytes } + +// HasSopsTopLevelKey checks whether a top-level "sops" key exists. +func (store *Store) HasSopsTopLevelKey(branch sops.TreeBranch) bool { + return stores.HasSopsTopLevelKey(branch) +} + +// HasSopsTopLevelKey checks whether a top-level "sops" key exists. +func (store *BinaryStore) HasSopsTopLevelKey(branch sops.TreeBranch) bool { + return stores.HasSopsTopLevelKey(branch) +} diff --git a/stores/stores.go b/stores/stores.go index e4b17289d..f4f8f2354 100644 --- a/stores/stores.go +++ b/stores/stores.go @@ -506,3 +506,13 @@ var ExampleFlatTree = sops.Tree{ }, }, } + +// HasSopsTopLevelKey returns true if the given branch has a top-level key called "sops". +func HasSopsTopLevelKey(branch sops.TreeBranch) bool { + for _, b := range branch { + if b.Key == "sops" { + return true + } + } + return false +} diff --git a/stores/yaml/store.go b/stores/yaml/store.go index 4d036f366..86ca00672 100644 --- a/stores/yaml/store.go +++ b/stores/yaml/store.go @@ -417,3 +417,8 @@ func (store *Store) EmitExample() []byte { } return bytes } + +// HasSopsTopLevelKey checks whether a top-level "sops" key exists. +func (store *Store) HasSopsTopLevelKey(branch sops.TreeBranch) bool { + return stores.HasSopsTopLevelKey(branch) +} diff --git a/stores/yaml/store_test.go b/stores/yaml/store_test.go index 41b3004d0..fb53d9ed3 100644 --- a/stores/yaml/store_test.go +++ b/stores/yaml/store_test.go @@ -380,4 +380,21 @@ func TestIndent1(t *testing.T) { assert.Nil(t, err) assert.Equal(t, string(INDENT_1_OUT), string(bytes)) assert.Equal(t, INDENT_1_OUT, bytes) -} \ No newline at end of file +} + +func TestHasSopsTopLevelKey(t *testing.T) { + ok := (&Store{}).HasSopsTopLevelKey(sops.TreeBranch{ + sops.TreeItem{ + Key: "sops", + Value: "value", + }, + }) + assert.Equal(t, ok, true) + ok = (&Store{}).HasSopsTopLevelKey(sops.TreeBranch{ + sops.TreeItem{ + Key: "sops_", + Value: "value", + }, + }) + assert.Equal(t, ok, false) +}