mirror of
https://github.com/getsops/sops.git
synced 2026-02-05 12:45:21 +01:00
Merge branch 'main' into cg/minimum-sops-config
This commit is contained in:
@@ -24,7 +24,8 @@ func NewStore(c *config.INIStoreConfig) *Store {
|
||||
}
|
||||
|
||||
func (store Store) encodeTree(branches sops.TreeBranches) ([]byte, error) {
|
||||
iniFile := ini.Empty()
|
||||
iniFile := ini.Empty(ini.LoadOptions{AllowNonUniqueSections: true})
|
||||
iniFile.DeleteSection(ini.DefaultSection)
|
||||
for _, branch := range branches {
|
||||
for _, item := range branch {
|
||||
if _, ok := item.Key.(sops.Comment); ok {
|
||||
@@ -95,7 +96,7 @@ func (store Store) iniFromTreeBranches(branches sops.TreeBranches) ([]byte, erro
|
||||
}
|
||||
|
||||
func (store Store) treeBranchesFromIni(in []byte) (sops.TreeBranches, error) {
|
||||
iniFile, err := ini.Load(in)
|
||||
iniFile, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -143,7 +144,7 @@ func (store Store) treeItemFromSection(section *ini.Section) (sops.TreeItem, err
|
||||
|
||||
// LoadEncryptedFile loads encrypted INI file's bytes onto a sops.Tree runtime object
|
||||
func (store *Store) LoadEncryptedFile(in []byte) (sops.Tree, error) {
|
||||
iniFileOuter, err := ini.Load(in)
|
||||
iniFileOuter, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, in)
|
||||
if err != nil {
|
||||
return sops.Tree{}, err
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package ini
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/getsops/sops/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDecodeIni(t *testing.T) {
|
||||
@@ -127,6 +127,55 @@ func TestEncodeIniWithEscaping(t *testing.T) {
|
||||
assert.Equal(t, expected, branches)
|
||||
}
|
||||
|
||||
func TestEncodeIniWithDuplicateSections(t *testing.T) {
|
||||
branches := sops.TreeBranches{
|
||||
sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "DEFAULT",
|
||||
Value: interface{}(sops.TreeBranch(nil)),
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
Value: sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "baz",
|
||||
Value: "3.0",
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "qux",
|
||||
Value: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
Value: sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "baz",
|
||||
Value: "3.0",
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "qux",
|
||||
Value: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
out, err := Store{}.iniFromTreeBranches(branches)
|
||||
assert.Nil(t, err)
|
||||
expected, _ := Store{}.treeBranchesFromIni(out)
|
||||
assert.Equal(t, expected, branches)
|
||||
}
|
||||
|
||||
func TestUnmarshalMetadataFromNonSOPSFile(t *testing.T) {
|
||||
data := []byte(`hello=2`)
|
||||
store := Store{}
|
||||
|
||||
@@ -186,18 +186,20 @@ func (store Store) encodeValue(v interface{}) ([]byte, error) {
|
||||
|
||||
func (store Store) encodeArray(array []interface{}) ([]byte, error) {
|
||||
out := "["
|
||||
for i, item := range array {
|
||||
empty := true
|
||||
for _, item := range array {
|
||||
if _, ok := item.(sops.Comment); ok {
|
||||
continue
|
||||
}
|
||||
if !empty {
|
||||
out += ","
|
||||
}
|
||||
v, err := store.encodeValue(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out += string(v)
|
||||
if i != len(array)-1 {
|
||||
out += ","
|
||||
}
|
||||
empty = false
|
||||
}
|
||||
out += "]"
|
||||
return []byte(out), nil
|
||||
@@ -205,10 +207,14 @@ func (store Store) encodeArray(array []interface{}) ([]byte, error) {
|
||||
|
||||
func (store Store) encodeTree(tree sops.TreeBranch) ([]byte, error) {
|
||||
out := "{"
|
||||
for i, item := range tree {
|
||||
empty := true
|
||||
for _, item := range tree {
|
||||
if _, ok := item.Key.(sops.Comment); ok {
|
||||
continue
|
||||
}
|
||||
if !empty {
|
||||
out += ","
|
||||
}
|
||||
v, err := store.encodeValue(item.Value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error encoding value %s: %s", v, err)
|
||||
@@ -218,9 +224,7 @@ func (store Store) encodeTree(tree sops.TreeBranch) ([]byte, error) {
|
||||
return nil, fmt.Errorf("Error encoding key %s: %s", k, err)
|
||||
}
|
||||
out += string(k) + `: ` + string(v)
|
||||
if i != len(tree)-1 {
|
||||
out += ","
|
||||
}
|
||||
empty = false
|
||||
}
|
||||
return []byte(out + "}"), nil
|
||||
}
|
||||
@@ -326,6 +330,7 @@ func (store *Store) EmitEncryptedFile(in sops.Tree) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error marshaling to json: %s", err)
|
||||
}
|
||||
out = append(out, '\n')
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -336,6 +341,7 @@ func (store *Store) EmitPlainFile(in sops.TreeBranches) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error marshaling to json: %s", err)
|
||||
}
|
||||
out = append(out, '\n')
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,8 @@ func TestDecodeJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
}
|
||||
`
|
||||
expected := sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "glossary",
|
||||
@@ -312,7 +313,8 @@ func TestEncodeJSONArrayOfObjects(t *testing.T) {
|
||||
},
|
||||
2
|
||||
]
|
||||
}`
|
||||
}
|
||||
`
|
||||
store := Store{
|
||||
config: config.JSONStoreConfig{
|
||||
Indent: -1,
|
||||
@@ -446,7 +448,8 @@ func TestIndentTwoSpaces(t *testing.T) {
|
||||
},
|
||||
2
|
||||
]
|
||||
}`
|
||||
}
|
||||
`
|
||||
store := Store{
|
||||
config: config.JSONStoreConfig{
|
||||
Indent: 2,
|
||||
@@ -488,7 +491,8 @@ func TestIndentDefault(t *testing.T) {
|
||||
},
|
||||
2
|
||||
]
|
||||
}`
|
||||
}
|
||||
`
|
||||
store := Store{
|
||||
config: config.JSONStoreConfig{
|
||||
Indent: -1,
|
||||
@@ -530,7 +534,8 @@ func TestNoIndent(t *testing.T) {
|
||||
},
|
||||
2
|
||||
]
|
||||
}`
|
||||
}
|
||||
`
|
||||
store := Store{
|
||||
config: config.JSONStoreConfig{
|
||||
Indent: 0,
|
||||
@@ -539,4 +544,94 @@ func TestNoIndent(t *testing.T) {
|
||||
out, err := store.EmitPlainFile(tree.Branches)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, string(out))
|
||||
|
||||
}
|
||||
|
||||
func TestConflictingAttributes(t *testing.T) {
|
||||
// See https://stackoverflow.com/a/23195243
|
||||
// Duplicate keys in json is technically valid, but discouraged.
|
||||
// Implementations may handle them differently. ECMA-262 says
|
||||
//
|
||||
// > In the case where there are duplicate name Strings within an object,
|
||||
// > lexically preceding values for the same key shall be overwritten.
|
||||
|
||||
data := `
|
||||
{
|
||||
"hello": "Sops config file",
|
||||
"hello": "Doubles are ok",
|
||||
"hello": ["repeatedly"],
|
||||
"hello": 3.14
|
||||
}
|
||||
`
|
||||
s := new(Store)
|
||||
_, err := s.LoadPlainFile([]byte(data))
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestComments(t *testing.T) {
|
||||
tree := sops.Tree{
|
||||
Branches: sops.TreeBranches{
|
||||
sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
Value: []interface{}{
|
||||
sops.Comment{Value: " comment 0"},
|
||||
sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: sops.Comment{Value: " comment 1"},
|
||||
Value: nil,
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
Value: 3,
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: sops.Comment{Value: " comment 2"},
|
||||
Value: nil,
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: sops.Comment{Value: " comment 3"},
|
||||
Value: nil,
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "bar",
|
||||
Value: false,
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: sops.Comment{Value: " comment 4"},
|
||||
Value: nil,
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: sops.Comment{Value: " comment 5"},
|
||||
Value: nil,
|
||||
},
|
||||
},
|
||||
sops.Comment{Value: " comment 6"},
|
||||
sops.Comment{Value: " comment 7"},
|
||||
2,
|
||||
sops.Comment{Value: " comment 8"},
|
||||
sops.Comment{Value: " comment 9"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expected := `{
|
||||
"foo": [
|
||||
{
|
||||
"foo": 3,
|
||||
"bar": false
|
||||
},
|
||||
2
|
||||
]
|
||||
}
|
||||
`
|
||||
store := Store{
|
||||
config: config.JSONStoreConfig{
|
||||
Indent: 2,
|
||||
},
|
||||
}
|
||||
out, err := store.EmitPlainFile(tree.Branches)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, string(out))
|
||||
}
|
||||
|
||||
@@ -307,6 +307,13 @@ func (store *Store) LoadEncryptedFile(in []byte) (sops.Tree, error) {
|
||||
// sops.Tree runtime object
|
||||
func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranches, error) {
|
||||
var branches sops.TreeBranches
|
||||
if len(in) > 0 {
|
||||
// This is needed to make the yaml-decoder check for uniqueness of keys
|
||||
// Can probably be removed when https://github.com/go-yaml/yaml/issues/814 is merged.
|
||||
if err := yaml.NewDecoder(bytes.NewReader(in)).Decode(make(map[string]interface{})); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
d := yaml.NewDecoder(bytes.NewReader(in))
|
||||
for {
|
||||
var data yaml.Node
|
||||
@@ -322,6 +329,12 @@ func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranches, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error unmarshaling input YAML: %s", err)
|
||||
}
|
||||
// Prevent use of reserved keywords
|
||||
for _, item := range branch {
|
||||
if item.Key == stores.SopsMetadataKey {
|
||||
return nil, fmt.Errorf("YAML doc used reserved word '%v'", item.Key)
|
||||
}
|
||||
}
|
||||
branches = append(branches, branch)
|
||||
}
|
||||
return branches, nil
|
||||
|
||||
@@ -62,19 +62,19 @@ key4: *bar
|
||||
var ALIASES_BRANCHES = sops.TreeBranches{
|
||||
sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "key1",
|
||||
Key: "key1",
|
||||
Value: []interface{}{
|
||||
"foo",
|
||||
},
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "key2",
|
||||
Key: "key2",
|
||||
Value: []interface{}{
|
||||
"foo",
|
||||
},
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "key3",
|
||||
Key: "key3",
|
||||
Value: sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
@@ -87,7 +87,7 @@ var ALIASES_BRANCHES = sops.TreeBranches{
|
||||
},
|
||||
},
|
||||
sops.TreeItem{
|
||||
Key: "key4",
|
||||
Key: "key4",
|
||||
Value: sops.TreeBranch{
|
||||
sops.TreeItem{
|
||||
Key: "foo",
|
||||
@@ -237,7 +237,6 @@ prometheus-node-exporter:
|
||||
- --collector.filesystem.ignored-fs-types=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$
|
||||
`)
|
||||
|
||||
|
||||
func TestUnmarshalMetadataFromNonSOPSFile(t *testing.T) {
|
||||
data := []byte(`hello: 2`)
|
||||
_, err := (&Store{}).LoadEncryptedFile(data)
|
||||
@@ -398,3 +397,35 @@ func TestHasSopsTopLevelKey(t *testing.T) {
|
||||
})
|
||||
assert.Equal(t, ok, false)
|
||||
}
|
||||
|
||||
func TestDuplicateAttributes(t *testing.T) {
|
||||
// Duplicate keys are _not_ valid yaml.
|
||||
//
|
||||
// See https://yaml.org/spec/1.2.2/#mapping
|
||||
// > The content of a mapping node is an unordered set of key/value node pairs,
|
||||
// > with the restriction that each of the keys is unique.
|
||||
//
|
||||
data := `
|
||||
hello: Sops config file
|
||||
hello: Duplicates are not ok
|
||||
rootunique:
|
||||
key2: "value"
|
||||
key2: "foo"
|
||||
`
|
||||
s := new(Store)
|
||||
_, err := s.LoadPlainFile([]byte(data))
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, `yaml: unmarshal errors:
|
||||
line 3: mapping key "hello" already defined at line 2`, err.Error())
|
||||
}
|
||||
|
||||
func TestReservedAttributes(t *testing.T) {
|
||||
data := `
|
||||
hello: Sops config file
|
||||
sops: The attribute 'sops' must be rejected, otherwise the file cannot be opened later on
|
||||
`
|
||||
s := new(Store)
|
||||
_, err := s.LoadPlainFile([]byte(data))
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, `YAML doc used reserved word 'sops'`, err.Error())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user