1
0
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:
Charlie Getzen
2025-03-27 23:04:20 -07:00
committed by GitHub
63 changed files with 4372 additions and 1487 deletions

View File

@@ -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
}

View File

@@ -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{}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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

View File

@@ -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())
}